diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 247818a52..b712b8b7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '8', '11', '15', '16' ] + java: [ '16' ] fail-fast: false steps: - uses: actions/checkout@v2 @@ -65,8 +65,7 @@ jobs: - name: Apply Patches run: | - ./gradlew setupUpstream - ./gradlew applyPatches + ./gradlew setupUpstream applyPatches - name: Pull Maven Cache uses: actions/cache@v2 @@ -77,11 +76,11 @@ jobs: - name: Build Yatopia run: | - ./gradlew clean build paperclip + ./gradlew clean build yatoclip - name: Upload Artifact uses: actions/upload-artifact@v2 with: name: Yatopia-${{ matrix.java }} - path: yatopia-${{ steps.mcver.outputs.mcver }}-paperclip.jar + path: yatopia-${{ steps.mcver.outputs.mcver }}-yatoclip.jar diff --git a/Jenkinsfile b/Jenkinsfile index 01731e4e7..d16edf576 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,7 +9,7 @@ pipeline { stages { stage('Cleanup') { tools { - jdk "OpenJDK 8" + jdk "OpenJDK 16" } steps { scmSkip(deleteBuild: true, skipPattern:'.*\\[CI-SKIP\\].*') @@ -18,7 +18,7 @@ pipeline { sh 'rm -rf ./Paper/Paper-API ./Paper/Paper-Server' // sh 'mv ./Paper/work/Minecraft ./ || true' - sh 'rm -fr ./Paper/work/*' + // sh 'rm -fr ./Paper/work/*' // sh 'mv ./Minecraft ./Paper/work/ || true' @@ -29,7 +29,7 @@ pipeline { } stage('Init project & submodules') { tools { - jdk "OpenJDK 8" + jdk "OpenJDK 16" } steps { withMaven( @@ -43,7 +43,7 @@ pipeline { } stage('Decompile & apply patches') { tools { - jdk "OpenJDK 8" + jdk "OpenJDK 16" } steps { withMaven( @@ -60,7 +60,7 @@ pipeline { } stage('Build') { tools { - jdk "OpenJDK 8" + jdk "OpenJDK 16" } steps { withMaven( @@ -70,20 +70,13 @@ pipeline { ) { withCredentials([usernamePassword(credentialsId: 'jenkins-deploy', usernameVariable: 'ORG_GRADLE_PROJECT_mavenUsername', passwordVariable: 'ORG_GRADLE_PROJECT_mavenPassword')]) { sh ''' - ./gradlew build publish + ./gradlew build publish yatoclip mkdir -p "./target" basedir=$(pwd) paperworkdir="$basedir/Paper/work" mcver=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) - - patchedJarPath="$basedir/Yatopia-Server/build/libs/yatopia-server-$mcver-R0.1-SNAPSHOT.jar" - vanillaJarPath="$paperworkdir/Minecraft/$mcver/$mcver.jar" - cd "$paperworkdir/Paperclip" - mvn -T 2C clean package -Dmcver="$mcver" -Dpaperjar="$patchedJarPath" -Dvanillajar="$vanillaJarPath" -Dstyle.color=never - cd "$basedir" - - cp -v "$paperworkdir/Paperclip/assembly/target/paperclip-$mcver.jar" "./target/yatopia-$mcver-paperclip-b$BUILD_NUMBER.jar" + cp -v "./yatopia-$mcver-yatoclip.jar" "./target/yatopia-$mcver-yatoclip-b$BUILD_NUMBER.jar" ''' } } diff --git a/PATCHES.md b/PATCHES.md index 46bed4dac..cce9580b0 100644 --- a/PATCHES.md +++ b/PATCHES.md @@ -110,6 +110,8 @@ This is an overview of all the patches that are currently used. | server | Breedable parrots | BillyGalbreath | | | api | Bring back server name | William Blake Galbreath | | | server | Bring back server name | William Blake Galbreath | | +| server | C2ME Port | ishland | Simon Gardling | +| server | C2ME update | Simon Gardling | | | server | Cache climbing check for activation | Paul Sauve | | | server | Cache coordinate key for micro opt | Paul Sauve | | | server | Cache entityhuman display name | Paul Sauve | | @@ -237,6 +239,7 @@ This is an overview of all the patches that are currently used. | server | Fix the dead lagging the server | William Blake Galbreath | | | server | Fix vanilla command permission handler | William Blake Galbreath | | | server | Flying squids! Oh my! | William Blake Galbreath | | +| server | Force world save | ishland | | | api | Full netherite armor grants fire resistance | BillyGalbreath | | | server | Full netherite armor grants fire resistance | BillyGalbreath | | | server | Gamemode extra permissions | BillyGalbreath | | @@ -306,6 +309,7 @@ This is an overview of all the patches that are currently used. | server | Movement options for armor stands | Mariell Hoversholm | | | server | Multi-Threaded Server Ticking Vanilla | Spottedleaf | | | server | Multi-Threaded ticking CraftBukkit | Spottedleaf | | +| server | Multi-threaded World Upgrade | ishland | | | server | Name craft scheduler threads according to the plugin using | Spottedleaf | | | server | New nbt cache | Hugo Planque | ishland | | server | Nuke streams off BlockPosition | Ivan Pekov | | @@ -455,10 +459,13 @@ This is an overview of all the patches that are currently used. | server | Zombie horse naturally spawn | William Blake Galbreath | | | server | add config for logging login location | Simon Gardling | | | server | dont load chunks for physics | Aikar | | +| api | java 16 | Simon Gardling | | +| server | java 16 | Simon Gardling | | | server | lithium DataTrackerMixin | JellySquid | tr7zw | | server | lithium HashedList | JellySquid | | | server | lithium MixinBox | JellySquid | | | server | lithium MixinDirection | JellySquid | | +| server | lithium MultiNoiseBiomeSourceMixin | ishland | | | server | lithium NoiseChunkGeneratorMixin | JellySquid | | | server | lithium PerlinNoiseSamplerMixin | JellySquid | Bud Gidiere | | server | lithium VoronoiBiomeAccessTypeMixin | JellySquid | | diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java index 3603af883..0a57372eb 100644 --- a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java @@ -1,51 +1,41 @@ package org.yatopiamc.yatoclip; -import java.io.Serializable; import java.util.Collections; +import java.util.Map; import java.util.Objects; import java.util.Set; public class PatchesMetadata { public final Set patches; - public final Set relocations; public final Set copyExcludes; + public final Map relocationMapping; + public final Map relocationInvertedMapping; - public PatchesMetadata(Set patches, Set relocations, Set copyExcludes) { + public PatchesMetadata(Set patches, Set copyExcludes, Map relocationMapping, Map relocationInvertedMapping) { Objects.requireNonNull(copyExcludes); this.copyExcludes = Collections.unmodifiableSet(copyExcludes); - Objects.requireNonNull(relocations); - this.relocations = Collections.unmodifiableSet(relocations); Objects.requireNonNull(patches); this.patches = Collections.unmodifiableSet(patches); + Objects.requireNonNull(relocationMapping); + this.relocationMapping = relocationMapping; + Objects.requireNonNull(relocationInvertedMapping); + this.relocationInvertedMapping = relocationInvertedMapping; } public static class PatchMetadata { - public final String name; + public final String originalName; + public final String targetName; public final String originalHash; public final String targetHash; public final String patchHash; - public PatchMetadata(String name, String originalHash, String targetHash, String patchHash) { - this.name = name; + public PatchMetadata(String originalName, String targetName, String originalHash, String targetHash, String patchHash) { + this.originalName = originalName; + this.targetName = targetName; this.originalHash = originalHash; this.targetHash = targetHash; this.patchHash = patchHash; } } - - public static class Relocation implements Serializable { - - public final String from; - public final String to; - public final boolean includeSubPackages; - - public Relocation(String from, String to, boolean includeSubPackages) { - Objects.requireNonNull(from); - Objects.requireNonNull(to); - this.from = from.replaceAll("\\.", "/"); - this.to = to.replaceAll("\\.", "/"); - this.includeSubPackages = includeSubPackages; - } - } } diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java index 905c3795c..270432f97 100644 --- a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java @@ -70,14 +70,14 @@ private static void applyMappingsAndPatches() throws IOException { SpecialSourceLauncher.setSpecialSourceJar(buildData.resolve("bin").resolve("SpecialSource-2.jar").toFile()); System.err.println("Applying class mapping..."); SpecialSourceLauncher.runProcess( - "map", "--only", ".", "--only", "net/minecraft", "--auto-lvt", "BASIC", "--auto-member", "SYNTHETIC", + "map", "--only", ".", "--only", "net/minecraft", "--only", "com/mojang/math", "--auto-lvt", "BASIC", "--auto-member", "SYNTHETIC", "-i", vanillaJar.toAbsolutePath().toString(), "-m", buildData.resolve("mappings").resolve(buildDataInfo.classMappings).toAbsolutePath().toString(), "-o", classMappedJar.toAbsolutePath().toString() ); System.err.println("Applying member mapping..."); SpecialSourceLauncher.runProcess( - "map", "--only", ".", "--only", "net/minecraft", "--auto-member", "LOGGER", "--auto-member", "TOKENS", + "map", "--only", ".", "--only", "net/minecraft", "--only", "com/mojang/math", "--auto-member", "LOGGER", "--auto-member", "TOKENS", "-i", classMappedJar.toAbsolutePath().toString(), "-m", buildData.resolve("mappings").resolve(buildDataInfo.memberMappings).toAbsolutePath().toString(), "-o", memberMappedJar.toAbsolutePath().toString() diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java index 27d3fc25a..6ed1bdac3 100644 --- a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java @@ -57,7 +57,7 @@ static boolean isJarUpToDate(Path patchedJar) { MessageDigest digest = MessageDigest.getInstance("SHA-256"); try (ZipFile patchedZip = new ZipFile(patchedJar.toFile())) { for (PatchesMetadata.PatchMetadata patchMetadata : patchesMetadata.patches) { - ZipEntry zipEntry = patchedZip.getEntry(patchMetadata.name); + ZipEntry zipEntry = patchedZip.getEntry(patchMetadata.targetName); if (zipEntry == null || !patchMetadata.targetHash.equals(ServerSetup.toHex(digest.digest(IOUtils.toByteArray(patchedZip.getInputStream(zipEntry)))))) return false; } @@ -72,10 +72,10 @@ static boolean isJarUpToDate(Path patchedJar) { static void patchJar(Path memberMappedJar, Path patchedJar) { requireNonNull(memberMappedJar); requireNonNull(patchedJar); - if(!memberMappedJar.toFile().isFile()) throw new IllegalArgumentException(new FileNotFoundException()); + if(!memberMappedJar.toFile().isFile()) throw new IllegalArgumentException(new FileNotFoundException(memberMappedJar.toString())); try { patchedJar.toFile().getParentFile().mkdirs(); - final ThreadLocal classMappedZip = ThreadLocal.withInitial(() -> { + final ThreadLocal memberMappedZip = ThreadLocal.withInitial(() -> { try { return new ZipFile(memberMappedJar.toFile()); } catch (IOException e) { @@ -99,7 +99,7 @@ public Thread newThread(Runnable r) { r.run(); } finally { try { - classMappedZip.get().close(); + memberMappedZip.get().close(); } catch (IOException e) { e.printStackTrace(); } @@ -113,7 +113,7 @@ public Thread newThread(Runnable r) { try { final Set patchDataSet = patchesMetadata.patches.stream().map((PatchesMetadata.PatchMetadata metadata) -> new PatchData(CompletableFuture.supplyAsync(() -> { try { - return getPatchedBytes(classMappedZip.get(), digest.get(), metadata); + return getPatchedBytes(memberMappedZip.get(), digest.get(), metadata); } catch (IOException | CompressorException | InvalidHeaderException e) { throw new RuntimeException(e); } @@ -123,19 +123,19 @@ public Thread newThread(Runnable r) { patchedZip.setLevel(Deflater.BEST_SPEED); Set processed = new HashSet<>(); for (PatchData patchData : patchDataSet) { - putNextEntrySafe(patchedZip, patchData.metadata.name); + putNextEntrySafe(patchedZip, patchData.metadata.targetName); final byte[] patchedBytes = patchData.patchedBytesFuture.join(); patchedZip.write(patchedBytes); patchedZip.closeEntry(); - processed.add(patchData.metadata.name); + processed.add(patchData.metadata.targetName); } - ((Iterator) classMappedZip.get().entries()).forEachRemaining(zipEntry -> { - if (zipEntry.isDirectory() || processed.contains(applyRelocations(zipEntry.getName())) || patchesMetadata.copyExcludes.contains(zipEntry.getName())) + ((Iterator) memberMappedZip.get().entries()).forEachRemaining(zipEntry -> { + if (zipEntry.isDirectory() || processed.contains(patchesMetadata.relocationMapping.getOrDefault(zipEntry.getName(), zipEntry.getName())) || patchesMetadata.copyExcludes.contains(zipEntry.getName())) return; try { - InputStream in = classMappedZip.get().getInputStream(zipEntry); - putNextEntrySafe(patchedZip, zipEntry.getName()); + InputStream in = memberMappedZip.get().getInputStream(zipEntry); + putNextEntrySafe(patchedZip, patchesMetadata.relocationMapping.getOrDefault(zipEntry.getName(), zipEntry.getName())); patchedZip.write(IOUtils.toByteArray(in)); patchedZip.closeEntry(); } catch (Throwable t) { @@ -152,28 +152,31 @@ public Thread newThread(Runnable r) { } } - private static byte[] getPatchedBytes(ZipFile classMappedZip, MessageDigest digest, PatchesMetadata.PatchMetadata patchMetadata) throws IOException, CompressorException, InvalidHeaderException { + private static byte[] getPatchedBytes(ZipFile memberMappedZip, MessageDigest digest, PatchesMetadata.PatchMetadata patchMetadata) throws IOException, CompressorException, InvalidHeaderException { final byte[] originalBytes; - final ZipEntry originalEntry = classMappedZip.getEntry(applyRelocationsReverse(patchMetadata.name)); + final ZipEntry originalEntry = memberMappedZip.getEntry(patchMetadata.originalName); if (originalEntry != null) - try (final InputStream in = classMappedZip.getInputStream(originalEntry)) { + try (final InputStream in = memberMappedZip.getInputStream(originalEntry)) { originalBytes = IOUtils.toByteArray(in); } else originalBytes = new byte[0]; final byte[] patchBytes; - try (final InputStream in = YatoclipPatcher.class.getClassLoader().getResourceAsStream("patches/" + patchMetadata.name + ".patch")) { + try (final InputStream in = YatoclipPatcher.class.getClassLoader().getResourceAsStream("patches/" + patchMetadata.targetName + ".patch")) { if (in == null) - throw new FileNotFoundException(); + throw new FileNotFoundException("patches/" + patchMetadata.targetName + ".patch"); patchBytes = IOUtils.toByteArray(in); } - if (!patchMetadata.originalHash.equals(ServerSetup.toHex(digest.digest(originalBytes))) || !patchMetadata.patchHash.equals(ServerSetup.toHex(digest.digest(patchBytes)))) - throw new FileNotFoundException("Hash do not match"); + if (!patchMetadata.originalHash.equals(ServerSetup.toHex(digest.digest(originalBytes)))) + throw new FileNotFoundException(String.format("Hash do not match: original file: %s: expected %s but got %s", patchMetadata.originalName, patchMetadata.originalHash, ServerSetup.toHex(digest.digest(originalBytes)))); + + if (!patchMetadata.patchHash.equals(ServerSetup.toHex(digest.digest(patchBytes)))) + throw new FileNotFoundException(String.format("Hash do not match: patch file: %s: expected %s but got %s", patchMetadata.targetName + ".patch", patchMetadata.patchHash, ServerSetup.toHex(digest.digest(patchBytes)))); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); Patch.patch(originalBytes, patchBytes, byteOut); final byte[] patchedBytes = byteOut.toByteArray(); if (!patchMetadata.targetHash.equals(ServerSetup.toHex(digest.digest(patchedBytes)))) - throw new FileNotFoundException("Hash do not match"); + throw new FileNotFoundException(String.format("Hash do not match: target file: %s: expected %s but got %s", patchMetadata.targetName, patchMetadata.targetHash, ServerSetup.toHex(digest.digest(patchedBytes)))); return patchedBytes; } @@ -195,30 +198,6 @@ private static void putNextEntrySafe(ZipOutputStream patchedZip, String name) th patchedZip.putNextEntry(entry); } - private static String applyRelocations(String name) { - if (!name.endsWith(".class")) return name; - if (name.indexOf('/') == -1) - name = "/" + name; - for (PatchesMetadata.Relocation relocation : patchesMetadata.relocations) { - if (name.startsWith(relocation.from) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { - return relocation.to + name.substring(relocation.from.length()); - } - } - return name; - } - - private static String applyRelocationsReverse(String name) { - if (!name.endsWith(".class")) return name; - if (name.indexOf('/') == -1) - name = "/" + name; - for (PatchesMetadata.Relocation relocation : patchesMetadata.relocations) { - if (name.startsWith(relocation.to) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { - return relocation.from + name.substring(relocation.to.length()); - } - } - return name; - } - private static class PatchData { public final CompletableFuture patchedBytesFuture; diff --git a/build.gradle.kts b/build.gradle.kts index 6de486f1f..67804d5ef 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,10 +59,10 @@ subprojects { } java { - if(JavaVersion.VERSION_1_8 > JavaVersion.current()){ - error("This build must be run with Java 8 or better") + if(JavaVersion.VERSION_16 > JavaVersion.current()){ + error("This build must be run with Java 16 or later") } - sourceCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_16 targetCompatibility = JavaVersion.current() withSourcesJar() } diff --git a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java index ec7223fc9..814128e8b 100644 --- a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java +++ b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java @@ -1,7 +1,10 @@ package org.yatopiamc.yatoclip.gradle; +import com.github.jengelman.gradle.plugins.shadow.impl.RelocatorRemapper; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.gson.Gson; @@ -13,10 +16,12 @@ import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.internal.logging.progress.ProgressLogger; import org.gradle.internal.logging.progress.ProgressLoggerFactory; +import org.gradle.work.DisableCachingByDefault; import org.gradle.work.Incremental; import org.gradle.workers.WorkerExecutor; @@ -32,8 +37,10 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -42,6 +49,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +@DisableCachingByDefault public class MakePatchesTask extends DefaultTask { @OutputDirectory @@ -54,16 +62,16 @@ public class MakePatchesTask extends DefaultTask { @Incremental public File targetJar = null; - public Set getRelocations() { - return relocations; - } + @Internal + public RelocatorRemapper remapper = null; - public void setRelocations(Set relocations) { - this.relocations = relocations; + public RelocatorRemapper getRemapper() { + return remapper; } - @Input - public Set relocations; + public void setRemapper(RelocatorRemapper remapper) { + this.remapper = remapper; + } public File getOriginalJar() { return originalJar; @@ -159,11 +167,23 @@ public void genPatches() throws IOException, InterruptedException { } } })).build()); + BiMap relocationMap = HashBiMap.create(); + genPatches.progress("Calculating relocations"); + ((Iterator) originalZip.get().entries()).forEachRemaining(zipEntry -> { + if (zipEntry.isDirectory()) return; + relocationMap.put(zipEntry.getName(), remapper.map(zipEntry.getName())); + }); AtomicInteger current = new AtomicInteger(0); final int size = targetZip.get().size(); + HashSet processedEntries = new HashSet<>(size * 2, 0.5f); ((Iterator) targetZip.get().entries()).forEachRemaining(zipEntryT -> { genPatches.progress("Submitting tasks (" + current.incrementAndGet() + "/" + size + ")"); if (zipEntryT.isDirectory()) return; + if (processedEntries.contains(zipEntryT.getName())) { + getLogger().warn("Duplicate entry: " + zipEntryT.getName()); + return; + } + processedEntries.add(zipEntryT.getName()); executorService.execute(() -> { ZipEntry zipEntry = targetZip.get().getEntry(zipEntryT.getName()); final String child = zipEntry.getName(); @@ -172,7 +192,7 @@ public void genPatches() throws IOException, InterruptedException { outputFile.getParentFile().mkdirs(); final byte[] originalBytes; final byte[] targetBytes; - final ZipEntry oEntry = originalZip.get().getEntry(applyRelocationsReverse(child)); + final ZipEntry oEntry = originalZip.get().getEntry(relocationMap.inverse().getOrDefault(child, child)); try ( final InputStream oin = oEntry != null ? originalZip.get().getInputStream(oEntry) : null; final InputStream tin = targetZip.get().getInputStream(zipEntry); @@ -189,7 +209,7 @@ public void genPatches() throws IOException, InterruptedException { try (final OutputStream out = new FileOutputStream(outputFile)) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Diff.diff(originalBytes, targetBytes, byteArrayOutputStream); - patchMetadata.add(new PatchesMetadata.PatchMetadata(child, toHex(digestThreadLocal.get().digest(originalBytes)), toHex(digestThreadLocal.get().digest(targetBytes)), toHex(digestThreadLocal.get().digest(byteArrayOutputStream.toByteArray())))); + patchMetadata.add(new PatchesMetadata.PatchMetadata(relocationMap.inverse().getOrDefault(child, child), child, toHex(digestThreadLocal.get().digest(originalBytes)), toHex(digestThreadLocal.get().digest(targetBytes)), toHex(digestThreadLocal.get().digest(byteArrayOutputStream.toByteArray())))); out.write(byteArrayOutputStream.toByteArray()); } catch (Throwable t) { Throwables.throwIfUnchecked(t); @@ -202,7 +222,7 @@ public void genPatches() throws IOException, InterruptedException { genPatches.progress("Calculating exclusions"); Set copyExcludes = new HashSet<>(); ((Iterator) originalZip.get().entries()).forEachRemaining(zipEntry -> { - if(targetZip.get().getEntry(applyRelocations(zipEntry.getName())) == null) + if(targetZip.get().getEntry(relocationMap.getOrDefault(zipEntry.getName(), zipEntry.getName())) == null) copyExcludes.add(zipEntry.getName()); }); originalZip.get().close(); @@ -216,7 +236,7 @@ public void genPatches() throws IOException, InterruptedException { genPatches.progress("Writing patches metadata"); try (final OutputStream out = new FileOutputStream(new File(outputDir, "metadata.json")); final Writer writer = new OutputStreamWriter(out)) { - new Gson().toJson(new PatchesMetadata(patchMetadata, relocations, copyExcludes), writer); + new Gson().toJson(new PatchesMetadata(patchMetadata, copyExcludes, new HashMap<>(relocationMap), new HashMap<>(relocationMap.inverse())), writer); } /* @@ -234,30 +254,6 @@ public void genPatches() throws IOException, InterruptedException { } - private String applyRelocations(String name) { - if(!name.endsWith(".class")) return name; - if (name.indexOf('/') == -1) - name = "/" + name; - for (PatchesMetadata.Relocation relocation : relocations) { - if (name.startsWith(relocation.from) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { - return relocation.to + name.substring(relocation.from.length()); - } - } - return name; - } - - private String applyRelocationsReverse(String name) { - if(!name.endsWith(".class")) return name; - if (name.indexOf('/') == -1) - name = "/" + name; - for (PatchesMetadata.Relocation relocation : relocations) { - if (name.startsWith(relocation.to) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { - return relocation.from + name.substring(relocation.to.length()); - } - } - return name; - } - public static String toHex(final byte[] hash) { final StringBuilder sb = new StringBuilder(hash.length * 2); for (byte aHash : hash) { diff --git a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java index 4c7f0bbbc..80953ad67 100644 --- a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java +++ b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java @@ -1,51 +1,41 @@ package org.yatopiamc.yatoclip.gradle; -import java.io.Serializable; import java.util.Collections; +import java.util.Map; import java.util.Objects; import java.util.Set; public class PatchesMetadata { public final Set patches; - public final Set relocations; public final Set copyExcludes; + public final Map relocationMapping; + public final Map relocationInvertedMapping; - public PatchesMetadata(Set patches, Set relocations, Set copyExcludes) { + public PatchesMetadata(Set patches, Set copyExcludes, Map relocationMapping, Map relocationInvertedMapping) { Objects.requireNonNull(copyExcludes); this.copyExcludes = Collections.unmodifiableSet(copyExcludes); - Objects.requireNonNull(relocations); - this.relocations = Collections.unmodifiableSet(relocations); Objects.requireNonNull(patches); this.patches = Collections.unmodifiableSet(patches); + Objects.requireNonNull(relocationMapping); + this.relocationMapping = relocationMapping; + Objects.requireNonNull(relocationInvertedMapping); + this.relocationInvertedMapping = relocationInvertedMapping; } public static class PatchMetadata { - public final String name; + public final String originalName; + public final String targetName; public final String originalHash; public final String targetHash; public final String patchHash; - public PatchMetadata(String name, String originalHash, String targetHash, String patchHash) { - this.name = name; + public PatchMetadata(String originalName, String targetName, String originalHash, String targetHash, String patchHash) { + this.originalName = originalName; + this.targetName = targetName; this.originalHash = originalHash; this.targetHash = targetHash; this.patchHash = patchHash; } } - - public static class Relocation implements Serializable { - - public final String from; - public final String to; - public final boolean includeSubPackages; - - public Relocation(String from, String to, boolean includeSubPackages) { - Objects.requireNonNull(from); - Objects.requireNonNull(to); - this.from = from.replaceAll("\\.", "/"); - this.to = to.replaceAll("\\.", "/"); - this.includeSubPackages = includeSubPackages; - } - } } diff --git a/buildSrc/src/main/kotlin/ConfigureSubprojects.kt b/buildSrc/src/main/kotlin/ConfigureSubprojects.kt index 869055f39..252c32802 100644 --- a/buildSrc/src/main/kotlin/ConfigureSubprojects.kt +++ b/buildSrc/src/main/kotlin/ConfigureSubprojects.kt @@ -1,4 +1,8 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin +import com.github.jengelman.gradle.plugins.shadow.ShadowStats +import com.github.jengelman.gradle.plugins.shadow.impl.RelocatorRemapper +import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar // import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer import transformer.ModifiedLog4j2PluginsCacheFileTransformer @@ -26,6 +30,7 @@ import org.yatopiamc.yatoclip.gradle.PropertiesUtils import java.nio.charset.StandardCharsets.UTF_8 import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.ArrayList import kotlin.collections.HashSet internal fun Project.configureSubprojects() { @@ -77,7 +82,8 @@ private fun Project.configureYatoclipProject() { originalJar = rootProject.toothpick.paperDir.resolve("work").resolve("Minecraft") .resolve(rootProject.toothpick.minecraftVersion).resolve("${rootProject.toothpick.minecraftVersion}-m.jar") targetJar = rootProject.toothpick.serverProject.project.tasks.getByName("shadowJar").outputs.files.singleFile - setRelocations(rootProject.toothpick.serverProject.project.extensions.getByName("relocations") as HashSet) + // not sure why idea mark this as invalid + setRemapper(rootProject.toothpick.serverProject.project.extensions.getByName("relocations") as RelocatorRemapper?) dependsOn(rootProject.toothpick.serverProject.project.tasks.getByName("shadowJar")) doLast { val prop = Properties() @@ -174,13 +180,17 @@ private fun Project.configureServerProject() { into("META-INF/maven/io.papermc.paper/paper") } - val relocationSet = HashSet() + val relocationSet = ArrayList() // Don't like to do this but sadly have to do this for compatibility reasons - relocate("org.bukkit.craftbukkit", "org.bukkit.craftbukkit.v${toothpick.nmsPackage}") { - exclude("org.bukkit.craftbukkit.Main*") - } - relocationSet.add(PatchesMetadata.Relocation("", "net.minecraft.server.v${toothpick.nmsPackage}", false)) + val simpleRelocator = SimpleRelocator( + "org.bukkit.craftbukkit", + "org.bukkit.craftbukkit.v${toothpick.nmsPackage}", + listOf(), + listOf("org.bukkit.craftbukkit.Main*") + ) + relocate(simpleRelocator) + relocationSet.add(simpleRelocator) // Make sure we relocate deps the same as Paper et al. val dom = project.parsePom() ?: return@getting @@ -200,19 +210,18 @@ private fun Project.configureServerProject() { val rawString = relocation.search("rawString").firstOrNull()?.textContent?.toBoolean() ?: false if (pattern != "org.bukkit.craftbukkit") { // We handle cb ourselves val excludes = if (rawString) listOf("net/minecraft/data/Main*") else emptyList() - relocate( - ToothpickRelocator( + val toothpickRelocator = ToothpickRelocator( pattern, shadedPattern.replace("\${minecraft_version}", toothpick.nmsPackage), rawString, excludes = excludes ) - ) - relocationSet.add(PatchesMetadata.Relocation(pattern, shadedPattern, true)) + relocate(toothpickRelocator) + relocationSet.add(toothpickRelocator) } } } - project.extensions.add("relocations", relocationSet) + project.extensions.add("relocations", RelocatorRemapper(relocationSet, ShadowStats())) } tasks.getByName("build") { dependsOn(shadowJar) diff --git a/patches/api/0009-java-16.patch b/patches/api/0009-java-16.patch new file mode 100644 index 000000000..4d92a04ff --- /dev/null +++ b/patches/api/0009-java-16.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Fri, 23 Apr 2021 11:11:13 -0400 +Subject: [PATCH] java 16 + + +diff --git a/pom.xml b/pom.xml +index a6a2fd50e685f64afecac5da6aaaad6227a3731e..a3a11d11181763a212727ad81ae6167a292c6f1c 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -19,8 +19,8 @@ + + + +- 1.8 +- 1.8 ++ 16 ++ 16 + UTF-8 + 4.7.0 + diff --git a/patches/removed/server/0072-Multi-threaded-RegionFile-IO.patch b/patches/removed/server/0072-Multi-threaded-RegionFile-IO.patch new file mode 100644 index 000000000..daec76b45 --- /dev/null +++ b/patches/removed/server/0072-Multi-threaded-RegionFile-IO.patch @@ -0,0 +1,307 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sun, 15 Nov 2020 10:42:27 +0800 +Subject: [PATCH] Multi-threaded RegionFile IO + + +diff --git a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java +index 9fe91f9512ee8c2589fc8da76bda5f6d70c9fac4..8b81119cfdef81665a302d96cfada5bb43bc1077 100644 +--- a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java ++++ b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java +@@ -35,7 +35,7 @@ import java.util.function.Function; + * @see #scheduleSave(WorldServer, int, int, NBTTagCompound, NBTTagCompound, int) + * @see #loadChunkDataAsync(WorldServer, int, int, int, Consumer, boolean, boolean, boolean) + */ +-public final class PaperFileIOThread extends QueueExecutorThread { ++public final class PaperFileIOThread { // Yatopia + + public static final Logger LOGGER = MinecraftServer.LOGGER; + public static final NBTTagCompound FAILURE_VALUE = new NBTTagCompound(); +@@ -44,23 +44,84 @@ public final class PaperFileIOThread extends QueueExecutorThread { + + public static final PaperFileIOThread INSTANCE = new PaperFileIOThread(); + ++ /* Yatopia + static { + INSTANCE.start(); + } ++ */ + } + + private final AtomicLong writeCounter = new AtomicLong(); ++ // Yatopia start - multi-threaded RegionFile IO ++ private final com.ibm.asyncutil.locks.AsyncNamedLock regionFileLock = com.ibm.asyncutil.locks.AsyncNamedLock.createFair(); ++ private final PrioritizedTaskQueue queue; ++ private final PaperFileIOThread.FileIOExecutorThread receiver; ++ private final java.util.Set executorThreads = com.google.common.collect.Sets.newConcurrentHashSet(); ++ private final java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newFixedThreadPool( ++ org.yatopiamc.yatopia.server.YatopiaConfig.regionFileIOThreadPoolSize == -1 ? Math.min(Runtime.getRuntime().availableProcessors(), Integer.getInteger("paper.maxChunkThreads", 8)) : org.yatopiamc.yatopia.server.YatopiaConfig.regionFileIOThreadPoolSize, ++ new com.google.common.util.concurrent.ThreadFactoryBuilder() ++ .setNameFormat("Paper RegionFile IO Worker #%d") ++ .setPriority(Thread.NORM_PRIORITY - 1) ++ .setDaemon(true) ++ .setThreadFactory(r -> { ++ Thread thr = new Thread(r); ++ executorThreads.add(thr); ++ return thr; ++ }) ++ .setUncaughtExceptionHandler((t, e) -> { ++ LOGGER.fatal("Uncaught exception thrown from " + t.getName() + ", report this!", e); ++ executorThreads.remove(t); ++ }) ++ .build() ++ ); + + private PaperFileIOThread() { +- super(new PrioritizedTaskQueue<>(), (int)(1.0e6)); // 1.0ms spinwait time +- this.setName("Paper RegionFile IO Thread"); +- this.setPriority(Thread.NORM_PRIORITY - 1); // we keep priority close to normal because threads can wait on us +- this.setUncaughtExceptionHandler((final Thread unused, final Throwable thr) -> { +- LOGGER.fatal("Uncaught exception thrown from IO thread, report this!", thr); ++ queue = new PrioritizedTaskQueue<>(); ++ receiver = new PaperFileIOThread.FileIOExecutorThread(queue, (int) (1.0e6)); // 1.0ms spinwait time ++ receiver.setName("Paper RegionFile IO Task Receiver"); ++ receiver.setPriority(Thread.NORM_PRIORITY - 1); // we keep priority close to normal because threads can wait on us ++ receiver.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { ++ LOGGER.fatal("Uncaught exception thrown from " + thread.getName() + ", report this!", thr); + }); ++ receiver.start(); ++ ++ } ++ ++ public void flush() { ++ receiver.flush(); ++ final java.util.Set> runningTasks = new java.util.HashSet<>(receiver.runningTasks); ++ LOGGER.debug("Flushing Chunk IO: Waiting for {} futures", runningTasks.size()); ++ for(CompletableFuture future: runningTasks) { ++ try { ++ future.join(); ++ } catch (Throwable ignored) { ++ } ++ } ++ } ++ ++ private void queueTask(PrioritizedTaskQueue.PrioritizedTask newTask) { ++ queue.add(newTask); ++ receiver.notifyTasks(); ++ } ++ ++ public void close(final boolean wait) { ++ receiver.close(wait, true); ++ this.flush(); ++ executor.shutdown(); ++ while (wait && !executor.isTerminated()) { ++ try { ++ executor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); ++ } catch (InterruptedException e) { ++ } ++ } ++ } ++ ++ @SuppressWarnings("BooleanMethodIsAlwaysInverted") ++ public boolean isOnWorkerThread() { ++ return executorThreads.contains(Thread.currentThread()); + } + +- /* run() is implemented by superclass */ ++ // Yatopia end + + /* + * +@@ -394,6 +455,85 @@ public final class PaperFileIOThread extends QueueExecutorThread { + this.queueTask(new GeneralTask(priority, runnable)); + } + ++ // Yatopia start ++ public static final class RegionFileCoord { ++ ++ public final int x; ++ public final int z; ++ ++ public RegionFileCoord(int x, int z) { ++ this.x = x; ++ this.z = z; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ RegionFileCoord that = (RegionFileCoord) o; ++ return x == that.x && z == that.z; ++ } ++ ++ @Override ++ public int hashCode() { ++ return java.util.Objects.hash(x, z); ++ } ++ } ++ ++ final class FileIOExecutorThread extends QueueExecutorThread { ++ private final java.util.Set> runningTasks = com.google.common.collect.Sets.newConcurrentHashSet(); ++ ++ public FileIOExecutorThread(PrioritizedTaskQueue queue, long spinWaitTime) { ++ super(queue, spinWaitTime); ++ } ++ ++ @Override ++ protected void preMainLoop() { ++ runningTasks.removeIf(CompletableFuture::isDone); ++ } ++ ++ @Override ++ protected boolean pollTasks(boolean flushTasks) { ++ Runnable task; ++ boolean ret = false; ++ ++ while ((task = this.queue.poll()) != null) { ++ ret = true; ++ if (task instanceof ChunkDataTask) { ++ ChunkDataTask chunkDataTask = (ChunkDataTask) task; ++ runningTasks.add(regionFileLock.acquireLock(new RegionFileCoord(chunkDataTask.x >> 5, chunkDataTask.z >> 5)) ++ .thenApplyAsync(lockToken -> { ++ try { ++ chunkDataTask.run(); ++ } finally { ++ lockToken.releaseLock(); ++ } ++ return null; ++ }, executor) ++ .exceptionally(throwable -> { ++ LOGGER.fatal("Exception thrown from prioritized runnable task in thread '" + Thread.currentThread().getName() + "': " + IOUtil.genericToString(chunkDataTask), throwable); ++ return null; ++ }).toCompletableFuture()); ++ } else { ++ Runnable finalTask = task; ++ runningTasks.add(CompletableFuture.supplyAsync(() -> { ++ finalTask.run(); ++ return null; ++ }).exceptionally(throwable -> { ++ LOGGER.fatal("Exception thrown from prioritized runnable task in thread '" + Thread.currentThread().getName() + "': " + IOUtil.genericToString(finalTask), throwable); ++ return null; ++ })); ++ } ++ } ++ ++ if (flushTasks) { ++ this.handleFlushThreads(false); ++ } ++ ++ return ret; ++ } ++ } ++ // Yatopia end + static final class GeneralTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { + + private final Runnable run; +diff --git a/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java +index ee906b594b306906c170180a29a8b61997d05168..7e348fcb813707fee830082b826932e0bbba1c49 100644 +--- a/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java ++++ b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java +@@ -35,6 +35,7 @@ public class QueueExecutorThread { + @javax.annotation.Nullable + @Override + public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws java.io.IOException { +- if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ if (this.world != null && !com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.isOnWorkerThread()) { // Yatopia + NBTTagCompound ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE + .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), + true, false, true).join().poiData; +@@ -519,7 +519,7 @@ public class VillagePlace extends RegionFileSection { + + @Override + public void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws java.io.IOException { +- if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ if (this.world != null && !com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.isOnWorkerThread()) { // Yatopia + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( + this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null, + com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - writes are async, no need for priority +diff --git a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java b/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java +index 545bf94bf47f0c6e9da1a4c162e081cbb2cd8390..e8e8e1fed06f0c17631a134e3673a25549d7c86c 100644 +--- a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java ++++ b/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java +@@ -16,6 +16,7 @@ import org.bukkit.Bukkit; + import org.bukkit.command.Command; + import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; ++import com.google.common.base.Preconditions; + + public class YatopiaConfig { + +@@ -282,4 +283,10 @@ public class YatopiaConfig { + allowThreadedFeatures = getBoolean("settings.c2me.allow-threaded-features", allowThreadedFeatures); + c2meThreads = getInt("settings.c2me.parallelism", c2meThreads); + } ++ ++ public static int regionFileIOThreadPoolSize = -1; ++ private static void multiThreadedRegionFile() { ++ regionFileIOThreadPoolSize = getInt("settings.threads.regionfile", -1); ++ Preconditions.checkArgument(regionFileIOThreadPoolSize == -1 || regionFileIOThreadPoolSize > 0, "Invalid settings.threads.regionfile in yatopia.yml"); ++ } + } diff --git a/patches/server/0067-C2ME-Port.patch b/patches/server/0067-C2ME-Port.patch new file mode 100644 index 000000000..c91cb852b --- /dev/null +++ b/patches/server/0067-C2ME-Port.patch @@ -0,0 +1,653 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Thu, 22 Apr 2021 17:56:12 -0400 +Subject: [PATCH] C2ME Port + +Port of https://github.com/YatopiaMC/C2ME-fabric + +Co-authored-by: Simon Gardling + +diff --git a/pom.xml b/pom.xml +index 3cfc312c3f4f5d30421e15977ef2dfeac0c3c841..e8884c5f79f0550dd479074f1b69e8b9f7b68784 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -230,6 +230,12 @@ + commons-rng-core + 1.3 + ++ ++ ++ com.ibm.async ++ asyncutil ++ 0.1.0 ++ + + + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 06157bb07cce3ba24087ceaca7138b5609b37b5b..47f0604a891d46f688abd5daa6fb4de8b56305e3 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -374,6 +374,7 @@ public class PlayerChunk { + return either == null ? null : (Chunk) either.left().orElse(null); // CraftBukkit - decompile error + } + ++ @Nullable public IChunkAccess getCurrentChunk() { return this.f(); } // Yatopia - OBFHELPER + @Nullable + public IChunkAccess f() { + for (int i = PlayerChunk.CHUNK_STATUSES.size() - 1; i >= 0; --i) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index e94d9c784bde37f65c2fd081eacbd41b061cd1aa..e24f323561875c1ef313dd5ceb222f9a2a1251d3 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -154,8 +154,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final LongSet unloadQueue; + private boolean updatingChunksModified; + private final ChunkTaskQueueSorter p; +- private final Mailbox> mailboxWorldGen; ++ // private final Mailbox> mailboxWorldGen; // Yatopia + public final Mailbox> mailboxMain; // Paper - private -> public ++ private final ThreadLocal capturedRequiredStatus = new ThreadLocal<>(); // Yatopia + // Paper start + final Mailbox> mailboxLight; + public void addLightTask(PlayerChunk playerchunk, Runnable run) { +@@ -461,7 +462,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Paper end + + this.p = new ChunkTaskQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); +- this.mailboxWorldGen = this.p.a(threadedmailbox, false); ++ // this.mailboxWorldGen = this.p.a(threadedmailbox, false); // Yatopia + this.mailboxMain = this.p.a(mailbox, false); + this.mailboxLight = this.p.a(lightthreaded, false);// Paper + this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getDimensionManager().hasSkyLight(), threadedmailbox1, this.p.a(threadedmailbox1, false)); +@@ -1334,7 +1335,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return this.z.put(chunkcoordintpair.pair(), (byte) (chunkstatus_type == ChunkStatus.Type.PROTOCHUNK ? -1 : 1)); + } + +- private CompletableFuture> b(PlayerChunk playerchunk, ChunkStatus chunkstatus) { ++ private CompletableFuture> b(PlayerChunk playerchunk, ChunkStatus chunkstatus) { // Yarn: upgradeChunk ++ this.capturedRequiredStatus.set(chunkstatus); // Yatopia - C2ME port + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + CompletableFuture, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, chunkstatus.f(), (i) -> { + return this.a(chunkstatus, i); +@@ -1372,7 +1374,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return; + } + // Paper end +- this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); ++ ++ // Yatopia start - C2ME port ++ org.yatopiamc.c2me.common.threading.GlobalExecutors.scheduler.execute(runnable); ++ // this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // Yatopia ++ // Yatopia end + }).thenComposeAsync((either) -> { // Tuinity start - force competion on the main thread + return CompletableFuture.completedFuture(either); + }, this.mainInvokingExecutor); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 537c05601492306e7b37b11594f193c7c668e11b..46355a0956be3eb3fd5cf312caf079fbf5464d79 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -178,7 +178,17 @@ import org.bukkit.event.world.TimeSkipEvent; + import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity + import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia + +-public class WorldServer extends World implements GeneratorAccessSeed, NonBlockingWorldAccess { // Yatopia ++// Yatopia start ++import org.yatopiamc.c2me.common.threading.worldgen.IWorldGenLockable; ++import com.ibm.asyncutil.locks.AsyncLock; ++import com.ibm.asyncutil.locks.AsyncNamedLock; ++// Yatopia end ++ ++public class WorldServer extends World implements GeneratorAccessSeed, NonBlockingWorldAccess, IWorldGenLockable { // Yatopia // Yatopia - port C2ME ++ ++ private volatile AsyncLock worldGenSingleThreadedLock = null; // Yatopia - port C2ME ++ private volatile AsyncNamedLock worldGenChunkLock = null; ++ // Yatopia - port C2ME + + public static final BlockPosition a = new BlockPosition(100, 50, 0); + private static final Logger LOGGER = LogManager.getLogger(); +@@ -608,7 +618,23 @@ public class WorldServer extends World implements GeneratorAccessSeed, NonBlocki + + this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper + this.fakeTime = this.worldDataServer.getDayTime(); // Purpur ++ // Yatopia start - port C2ME ++ this.worldGenSingleThreadedLock = AsyncLock.createFair(); ++ this.worldGenChunkLock = AsyncNamedLock.createFair(); ++ // Yatopia end - port C2ME ++ } ++ ++ // Yatopia start - port C2ME ++ @Override ++ public AsyncLock getWorldGenSingleThreadedLock() { ++ return this.worldGenSingleThreadedLock; ++ } ++ ++ @Override ++ public AsyncNamedLock getWorldGenChunkLock() { ++ return this.worldGenChunkLock; + } ++ // Yatopia end - port C2ME + + // Tuinity start - optimise collision + public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks, +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +index e2b5d6155bebdbf99b0850de7f9e1f5d342f9e2f..30db0ba3674a85c8dd866fab94c5374ba203c5cd 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +@@ -14,7 +14,7 @@ import java.util.stream.Stream; + + public class WeightedList { + +- protected final List> list; public final List> getList() { return this.list; } // Paper - decompile conflict // Tuinity - OBFHELPER ++ public final List> list; public final List> getList() { return this.list; } // Paper - decompile conflict // Tuinity - OBFHELPER // Yatopia - protected -> public + private final Random b; + private final boolean isUnsafe; // Paper + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index f4a4d63a2e21b08580023cf0dcd15a68d192cf14..1802498d48493d3e63c999a067c71e65ea29a890 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -22,6 +22,9 @@ import net.minecraft.world.level.levelgen.HeightMap; + import net.minecraft.world.level.levelgen.WorldGenStage; + import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureManager; + import net.minecraft.world.level.lighting.LightEngine; ++import org.yatopiamc.c2me.common.threading.worldgen.ChunkStatusUtils; // Yatopia ++import org.yatopiamc.c2me.common.threading.worldgen.IWorldGenLockable; // Yatopia ++import java.util.function.Supplier; // Yatopia + + public class ChunkStatus { + +@@ -162,6 +165,7 @@ public class ChunkStatus { + return ichunkaccess.getChunkStatus().b(chunkstatus) && ichunkaccess.r(); + } + ++ public static ChunkStatus byDistanceFromFull(int level) { return a(level); } // Yatopia - OBFHELPER + public static ChunkStatus a(int i) { + return i >= ChunkStatus.q.size() ? ChunkStatus.EMPTY : (i < 0 ? ChunkStatus.FULL : (ChunkStatus) ChunkStatus.q.get(i)); + } +@@ -186,6 +190,14 @@ public class ChunkStatus { + this.t = chunkstatus == null ? 0 : chunkstatus.c() + 1; + } + ++ static { ++ // Yatopia start - C2ME port ++ for (ChunkStatus chunkStatus : IRegistry.CHUNK_STATUS) { ++ chunkStatus.calculateReducedTaskRadius(); ++ } ++ // Yatopia end ++ } ++ + public final int getStatusIndex() { return c(); } // Paper - OBFHELPER + public int c() { + return this.t; +@@ -200,8 +212,44 @@ public class ChunkStatus { + return this.u; + } + ++ // Yatopia start - C2ME port ++ private int reducedTaskRadius = -1; ++ ++ public void calculateReducedTaskRadius() { ++ if (this.getNeighborRadius() == 0) { ++ this.reducedTaskRadius = 0; ++ } else { ++ for (int i = 0; i <= this.getNeighborRadius(); i++) { ++ final ChunkStatus status = ChunkStatus.byDistanceFromFull(ChunkStatus.getTicketLevelOffset(this) + i); // TODO [VanillaCopy] from TACS getRequiredStatusForGeneration ++ if (status == ChunkStatus.STRUCTURE_STARTS) { ++ this.reducedTaskRadius = Math.min(this.getNeighborRadius(), i); ++ break; ++ } ++ } ++ } ++ //noinspection ConstantConditions ++ if ((Object) this == ChunkStatus.LIGHT) { ++ this.reducedTaskRadius = 1; ++ } ++ System.out.println(String.format("%s task radius: %d -> %d", this, this.getNeighborRadius(), this.reducedTaskRadius)); ++ } ++ // Yatopia end ++ + public CompletableFuture> a(WorldServer worldserver, ChunkGenerator chunkgenerator, DefinedStructureManager definedstructuremanager, LightEngineThreaded lightenginethreaded, Function>> function, List list) { +- return this.v.doWork(this, worldserver, chunkgenerator, definedstructuremanager, lightenginethreaded, function, list, (IChunkAccess) list.get(list.size() / 2)); ++ // Yatopia start - port C2ME ++ final IChunkAccess targetChunk = (IChunkAccess) list.get(list.size() / 2); ++ final Supplier>> generationTask = () -> ++ this.v.doWork(this, worldserver, chunkgenerator, definedstructuremanager, lightenginethreaded, function, list, targetChunk); ++ ++ if (targetChunk.getChunkStatus().isAtLeastStatus((ChunkStatus) (Object) this)) { ++ return generationTask.get(); ++ } else { ++ int lockRadius = org.yatopiamc.yatopia.server.YatopiaConfig.reduceLockRadius && this.reducedTaskRadius != -1 ? this.reducedTaskRadius : this.getNeighborRadius(); ++ //noinspection ConstantConditions ++ return ChunkStatusUtils.runChunkGenWithLock(targetChunk.getPos(), lockRadius, ((IWorldGenLockable) worldserver).getWorldGenChunkLock(), () -> ++ ChunkStatusUtils.getThreadingType((ChunkStatus) (Object) this).runTask(((IWorldGenLockable) worldserver).getWorldGenSingleThreadedLock(), generationTask)); ++ } ++ // Yatopia end + } + + public CompletableFuture> a(WorldServer worldserver, DefinedStructureManager definedstructuremanager, LightEngineThreaded lightenginethreaded, Function>> function, IChunkAccess ichunkaccess) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java +index 13983f3271d33ab6e4c7030de5865edbd7b0cd8a..7460f5c85800f0d3c6076bc944b10b5931ba22bf 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java +@@ -843,7 +843,7 @@ public class DefinedStructure { + private final Map> b; + + private a(List list) { +- this.b = Maps.newHashMap(); ++ this.b = new java.util.concurrent.ConcurrentHashMap<>(); // Yatopia - port C2ME + this.a = list; + } + +diff --git a/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayers.java b/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayers.java +index 5bbd71f2cf6db34dd01e8e209809a4661505aaf1..76995e812492d3fd0f9180525727174bf3d8c409 100644 +--- a/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayers.java ++++ b/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayers.java +@@ -13,7 +13,7 @@ import net.minecraft.world.level.newbiome.layer.traits.AreaTransformer2; + + public class GenLayers { + +- private static final Int2IntMap a = (Int2IntMap) SystemUtils.a((Object) (new Int2IntOpenHashMap()), (int2intopenhashmap) -> { ++ private static final Int2IntMap a = (Int2IntMap) SystemUtils.a((new Int2IntOpenHashMap()), (int2intopenhashmap) -> { // Yatopia - decompile fixes + a(int2intopenhashmap, GenLayers.Type.BEACH, 16); + a(int2intopenhashmap, GenLayers.Type.BEACH, 26); + a(int2intopenhashmap, GenLayers.Type.DESERT, 2); +@@ -154,9 +154,9 @@ public class GenLayers { + + public static GenLayer a(long i, boolean flag, int j, int k) { + boolean flag1 = true; +- AreaFactory areafactory = a(flag, j, k, (l) -> { ++ AreaFactory areafactory = () -> a(flag, j, k, (l) -> { // Yatopia + return new WorldGenContextArea(25, i, l); +- }); ++ }).make(); // Yatopia + + return new GenLayer(areafactory); + } +diff --git a/src/main/java/org/yatopiamc/c2me/common/threading/GlobalExecutors.java b/src/main/java/org/yatopiamc/c2me/common/threading/GlobalExecutors.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dcf55bc98818f98c1a7b6869306a40c11b842cdf +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/threading/GlobalExecutors.java +@@ -0,0 +1,25 @@ ++package org.yatopiamc.c2me.common.threading; ++ ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++ ++import java.util.concurrent.ScheduledThreadPoolExecutor; ++import java.util.concurrent.atomic.AtomicReference; ++ ++public class GlobalExecutors { ++ ++ public static final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor( ++ 1, ++ new ThreadFactoryBuilder().setNameFormat("C2ME scheduler").setDaemon(true).setPriority(Thread.NORM_PRIORITY - 1).setThreadFactory(r -> { ++ final Thread thread = new Thread(r); ++ GlobalExecutors.schedulerThread.set(thread); ++ return thread; ++ }).build() ++ ); ++ private static final AtomicReference schedulerThread = new AtomicReference<>(); ++ ++ public static void ensureSchedulerThread() { ++ if (Thread.currentThread() != schedulerThread.get()) ++ throw new IllegalStateException("Not on scheduler thread"); ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/ChunkStatusThreadingType.java b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/ChunkStatusThreadingType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5af95799cb4cd380f25a31f50f67a2bcb9c1bec5 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/ChunkStatusThreadingType.java +@@ -0,0 +1,45 @@ ++ ++package org.yatopiamc.c2me.common.threading.worldgen; ++ ++import com.google.common.base.Preconditions; ++import com.ibm.asyncutil.locks.AsyncLock; ++import com.mojang.datafixers.util.Either; ++ ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.server.level.PlayerChunk; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Function; ++import java.util.function.Supplier; ++ ++public enum ChunkStatusThreadingType { ++ ++ PARALLELIZED() { ++ @Override ++ public CompletableFuture> runTask(AsyncLock lock, Supplier>> completableFuture) { ++ return CompletableFuture.supplyAsync(completableFuture, WorldGenThreadingExecutorUtils.mainExecutor).thenCompose(Function.identity()); ++ } ++ }, ++ SINGLE_THREADED() { ++ @Override ++ public CompletableFuture> runTask(AsyncLock lock, Supplier>> completableFuture) { ++ Preconditions.checkNotNull(lock); ++ return lock.acquireLock().toCompletableFuture().thenComposeAsync(lockToken -> { ++ try { ++ return completableFuture.get(); ++ } finally { ++ lockToken.releaseLock(); ++ } ++ }, WorldGenThreadingExecutorUtils.mainExecutor); ++ } ++ }, ++ AS_IS() { ++ @Override ++ public CompletableFuture> runTask(AsyncLock lock, Supplier>> completableFuture) { ++ return completableFuture.get(); ++ } ++ }; ++ ++ public abstract CompletableFuture> runTask(AsyncLock lock, Supplier>> completableFuture); ++ ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/ChunkStatusUtils.java b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/ChunkStatusUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0c28024cf9a50f35e1a867188b2a3f8fbbccb3ce +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/ChunkStatusUtils.java +@@ -0,0 +1,59 @@ ++package org.yatopiamc.c2me.common.threading.worldgen; ++ ++import com.ibm.asyncutil.locks.AsyncLock; ++import com.ibm.asyncutil.locks.AsyncNamedLock; ++import org.yatopiamc.c2me.common.threading.GlobalExecutors; ++import org.yatopiamc.c2me.common.util.AsyncCombinedLock; ++import org.yatopiamc.c2me.common.util.AsyncNamedLockDelegateAsyncLock; ++ ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.ChunkCoordIntPair; ++ ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.List; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Function; ++import java.util.function.Supplier; ++ ++import static org.yatopiamc.c2me.common.threading.worldgen.ChunkStatusThreadingType.AS_IS; ++import static org.yatopiamc.c2me.common.threading.worldgen.ChunkStatusThreadingType.PARALLELIZED; ++import static org.yatopiamc.c2me.common.threading.worldgen.ChunkStatusThreadingType.SINGLE_THREADED; ++import org.yatopiamc.yatopia.server.YatopiaConfig; ++ ++public class ChunkStatusUtils { ++ ++ public static ChunkStatusThreadingType getThreadingType(final ChunkStatus status) { ++ if (status.equals(ChunkStatus.STRUCTURE_STARTS) ++ || status.equals(ChunkStatus.STRUCTURE_REFERENCES) ++ || status.equals(ChunkStatus.BIOMES) ++ || status.equals(ChunkStatus.NOISE) ++ || status.equals(ChunkStatus.SURFACE) ++ || status.equals(ChunkStatus.CARVERS) ++ || status.equals(ChunkStatus.LIQUID_CARVERS) ++ || status.equals(ChunkStatus.HEIGHTMAPS)) { ++ return PARALLELIZED; ++ } else if (status.equals(ChunkStatus.SPAWN)) { ++ return SINGLE_THREADED; ++ } else if (status.equals(ChunkStatus.FEATURES)) { ++ return YatopiaConfig.allowThreadedFeatures ? PARALLELIZED : SINGLE_THREADED; ++ } ++ return AS_IS; ++ } ++ ++ public static CompletableFuture runChunkGenWithLock(ChunkCoordIntPair target, int radius, AsyncNamedLock chunkLock, Supplier> action) { ++ return CompletableFuture.supplyAsync(() -> { ++ ArrayList fetchedLocks = new ArrayList<>((2 * radius + 1) * (2 * radius + 1)); ++ for (int x = target.x - radius; x <= target.x + radius; x++) ++ for (int z = target.z - radius; z <= target.z + radius; z++) ++ fetchedLocks.add(new ChunkCoordIntPair(x, z)); ++ ++ return new AsyncCombinedLock(chunkLock, new HashSet<>(fetchedLocks)).getFuture().thenComposeAsync(lockToken -> { ++ final CompletableFuture future = action.get(); ++ future.thenRun(lockToken::releaseLock); ++ return future; ++ }, GlobalExecutors.scheduler); ++ }, AsyncCombinedLock.lockWorker).thenCompose(Function.identity()); ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/IWorldGenLockable.java b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/IWorldGenLockable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b80923bcda9045968e0fad39f2e40b99dba135dc +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/IWorldGenLockable.java +@@ -0,0 +1,13 @@ ++package org.yatopiamc.c2me.common.threading.worldgen; ++ ++import com.ibm.asyncutil.locks.AsyncLock; ++import com.ibm.asyncutil.locks.AsyncNamedLock; ++import net.minecraft.world.level.ChunkCoordIntPair; ++ ++public interface IWorldGenLockable { ++ ++ AsyncLock getWorldGenSingleThreadedLock(); ++ ++ AsyncNamedLock getWorldGenChunkLock(); ++ ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/WorldGenThreadingExecutorUtils.java b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/WorldGenThreadingExecutorUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..215010825a18881f84d94ead66314b946d46d75b +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/threading/worldgen/WorldGenThreadingExecutorUtils.java +@@ -0,0 +1,17 @@ ++package org.yatopiamc.c2me.common.threading.worldgen; ++ ++import org.yatopiamc.c2me.common.util.C2MEForkJoinWorkerThreadFactory; ++ ++import java.util.concurrent.ForkJoinPool; ++import org.yatopiamc.yatopia.server.YatopiaConfig; ++ ++public class WorldGenThreadingExecutorUtils { ++ ++ public static final ForkJoinPool mainExecutor = new ForkJoinPool( ++ YatopiaConfig.c2meThreads, ++ new C2MEForkJoinWorkerThreadFactory("C2ME worldgen worker #%d", Thread.NORM_PRIORITY - 1), ++ null, ++ true ++ ); ++ ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java b/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9e34b74d9abecae4b386d49514ceb0d1f333e271 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java +@@ -0,0 +1,88 @@ ++package org.yatopiamc.c2me.common.util; ++ ++import com.google.common.collect.Sets; ++import com.ibm.asyncutil.locks.AsyncLock; ++import com.ibm.asyncutil.locks.AsyncNamedLock; ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import net.minecraft.world.level.ChunkCoordIntPair; ++ ++import java.util.Optional; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ForkJoinPool; ++import java.util.function.Function; ++import java.util.stream.Collectors; ++ ++public class AsyncCombinedLock { ++ ++ public static final ForkJoinPool lockWorker = new ForkJoinPool( ++ 2, ++ new C2MEForkJoinWorkerThreadFactory("C2ME lock worker #%d", Thread.NORM_PRIORITY - 1), ++ null, ++ true ++ ); ++ ++ private final AsyncNamedLock lock; ++ private final ChunkCoordIntPair[] names; ++ private final CompletableFuture future = new CompletableFuture<>(); ++ ++ public AsyncCombinedLock(AsyncNamedLock lock, Set names) { ++ this.lock = lock; ++ this.names = names.toArray(ChunkCoordIntPair[]::new); ++ lockWorker.execute(this::tryAcquire); ++ } ++ ++ private synchronized void tryAcquire() { // TODO optimize logic further ++ final LockEntry[] tryLocks = new LockEntry[names.length]; ++ boolean allAcquired = true; ++ for (int i = 0, namesLength = names.length; i < namesLength; i++) { ++ ChunkCoordIntPair name = names[i]; ++ final LockEntry entry = new LockEntry(name, this.lock.tryLock(name)); ++ tryLocks[i] = entry; ++ if (entry.lockToken.isEmpty()) { ++ allAcquired = false; ++ break; ++ } ++ } ++ if (allAcquired) { ++ future.complete(() -> { ++ for (LockEntry entry : tryLocks) { ++ //noinspection OptionalGetWithoutIsPresent ++ entry.lockToken.get().releaseLock(); // if it isn't present then something is really wrong ++ } ++ }); ++ } else { ++ boolean triedRelock = false; ++ for (LockEntry entry : tryLocks) { ++ if (entry == null) continue; ++ entry.lockToken.ifPresent(AsyncLock.LockToken::releaseLock); ++ if (!triedRelock && entry.lockToken.isEmpty()) { ++ this.lock.acquireLock(entry.name).thenCompose(lockToken -> { ++ lockToken.releaseLock(); ++ return CompletableFuture.runAsync(this::tryAcquire, lockWorker); ++ }); ++ triedRelock = true; ++ } ++ } ++ if (!triedRelock) { ++ // shouldn't happen at all... ++ lockWorker.execute(this::tryAcquire); ++ } ++ } ++ } ++ ++ public CompletableFuture getFuture() { ++ return future.thenApply(Function.identity()); ++ } ++ ++ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") ++ private static class LockEntry { ++ public final ChunkCoordIntPair name; ++ public final Optional lockToken; ++ ++ private LockEntry(ChunkCoordIntPair name, Optional lockToken) { ++ this.name = name; ++ this.lockToken = lockToken; ++ } ++ } ++} +diff --git a/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java b/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..119421953de58fbc928e14bf618b340ee6b2fe94 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java +@@ -0,0 +1,29 @@ ++package org.yatopiamc.c2me.common.util; ++ ++import com.ibm.asyncutil.locks.AsyncLock; ++import com.ibm.asyncutil.locks.AsyncNamedLock; ++ ++import java.util.Objects; ++import java.util.Optional; ++import java.util.concurrent.CompletionStage; ++ ++public class AsyncNamedLockDelegateAsyncLock implements AsyncLock { ++ ++ private final AsyncNamedLock delegate; ++ private final T name; ++ ++ public AsyncNamedLockDelegateAsyncLock(AsyncNamedLock delegate, T name) { ++ this.delegate = Objects.requireNonNull(delegate); ++ this.name = name; ++ } ++ ++ @Override ++ public CompletionStage acquireLock() { ++ return delegate.acquireLock(name); ++ } ++ ++ @Override ++ public Optional tryLock() { ++ return delegate.tryLock(name); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/util/C2MEForkJoinWorkerThreadFactory.java b/src/main/java/org/yatopiamc/c2me/common/util/C2MEForkJoinWorkerThreadFactory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ab5b9be9dcf67bdd9237fb7d21574155c2d52306 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/util/C2MEForkJoinWorkerThreadFactory.java +@@ -0,0 +1,39 @@ ++package org.yatopiamc.c2me.common.util; ++ ++import java.util.concurrent.ForkJoinPool; ++import java.util.concurrent.ForkJoinWorkerThread; ++import java.util.concurrent.atomic.AtomicLong; ++ ++public class C2MEForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { ++ private final AtomicLong serial = new AtomicLong(0); ++ private final String namePattern; ++ private final int priority; ++ ++ public C2MEForkJoinWorkerThreadFactory(String namePattern, int priority) { ++ this.namePattern = namePattern; ++ this.priority = priority; ++ } ++ ++ @Override ++ public ForkJoinWorkerThread newThread(ForkJoinPool pool) { ++ final C2MEForkJoinWorkerThread C2MEForkJoinWorkerThread = new C2MEForkJoinWorkerThread(pool); ++ C2MEForkJoinWorkerThread.setName(String.format(namePattern, serial.incrementAndGet())); ++ C2MEForkJoinWorkerThread.setPriority(priority); ++ C2MEForkJoinWorkerThread.setDaemon(true); ++ return C2MEForkJoinWorkerThread; ++ } ++ ++ private static class C2MEForkJoinWorkerThread extends ForkJoinWorkerThread { ++ ++ /** ++ * Creates a ForkJoinWorkerThread operating in the given pool. ++ * ++ * @param pool the pool this thread works in ++ * @throws NullPointerException if pool is null ++ */ ++ protected C2MEForkJoinWorkerThread(ForkJoinPool pool) { ++ super(pool); ++ } ++ ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java b/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java +index fce7ce0efca340cf5820cdcbe010c9fdeae7cafc..1d1717d72ceb56594bc29f8a14437b61f911f817 100644 +--- a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java ++++ b/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java +@@ -271,4 +271,12 @@ public class YatopiaConfig { + fixProtocolLib = getBoolean("settings.fix-protocollib", fixProtocolLib); + } + ++ public static boolean allowThreadedFeatures = false; ++ public static int c2meThreads = Math.min(6, Runtime.getRuntime().availableProcessors()); ++ public static boolean reduceLockRadius = false; ++ private static void c2me() { ++ allowThreadedFeatures = getBoolean("settings.c2me.allow-threaded-features", allowThreadedFeatures); ++ c2meThreads = getInt("settings.c2me.parallelism", c2meThreads); ++ reduceLockRadius = getBoolean("settings.c2me.reduce-lock-radius", reduceLockRadius); ++ } + } diff --git a/patches/server/0068-Multi-threaded-World-Upgrade.patch b/patches/server/0068-Multi-threaded-World-Upgrade.patch new file mode 100644 index 000000000..0064b5fbe --- /dev/null +++ b/patches/server/0068-Multi-threaded-World-Upgrade.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Fri, 5 Feb 2021 19:34:00 +0800 +Subject: [PATCH] Multi-threaded World Upgrade + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 15b972b4a93b8fe3655acec47bc84b0f2c4620a6..67fdde82b7bff1b4973542acc4fe0d72f706d87e 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -292,6 +292,8 @@ public class Main { + WorldUpgrader worldupgrader = new WorldUpgrader(convertable_conversionsession, datafixer, immutableset, flag); + IChatBaseComponent ichatbasecomponent = null; + ++ long lastLocation = 0L; // Yatopia ++ long lastTime = System.nanoTime(); // Yatopia + while (!worldupgrader.b()) { + IChatBaseComponent ichatbasecomponent1 = worldupgrader.h(); + +@@ -300,13 +302,16 @@ public class Main { + Main.LOGGER.info(worldupgrader.h().getString()); + } + +- int i = worldupgrader.e(); +- +- if (i > 0) { +- int j = worldupgrader.f() + worldupgrader.g(); +- +- Main.LOGGER.info("{}% completed ({} / {} chunks)...", MathHelper.d((float) j / (float) i * 100.0F), j, i); +- } ++ // Yatopia start ++ long totalChunkCount = worldupgrader.getTotalChunkCount(); ++ if (totalChunkCount > 0) { ++ long processedCount = worldupgrader.getUpgradedChunkCount() + worldupgrader.getSkippedChunkCount(); ++ long currentTime = System.nanoTime(); ++ String speedRate = String.format("%.1f", (processedCount - lastLocation) / ((currentTime - lastTime) / 1_000_000_000.0)); ++ Main.LOGGER.info("{}% completed ({} / {} chunks) at {}cps...", MathHelper.d((float) processedCount / (float) totalChunkCount * 100.0F), processedCount, totalChunkCount, speedRate); ++ lastLocation = processedCount; ++ lastTime = currentTime; ++ } // Yatopia end + + if (!booleansupplier.getAsBoolean()) { + worldupgrader.a(); +diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +index cb394ec310712cc97d65afe068284b277d7f0483..12370f64b3590faabeef6e03db09a57bc87bc2ef 100644 +--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java ++++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +@@ -33,6 +33,13 @@ import net.minecraft.world.level.storage.Convertable; + import net.minecraft.world.level.storage.WorldPersistentData; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++// Yatopia start ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++// Yatopia end + + // CraftBukkit start + import net.minecraft.world.level.dimension.DimensionManager; +@@ -50,9 +57,9 @@ public class WorldUpgrader { + private volatile boolean h = true; + private volatile boolean i; + private volatile float j; +- private volatile int k; +- private volatile int l; +- private volatile int m; ++ private volatile long k; // Yatopia - int -> long ++ private volatile long l; // Yatopia - int -> long ++ private volatile long m; // Yatopia - int -> long + private final Object2FloatMap> n = Object2FloatMaps.synchronize(new Object2FloatOpenCustomHashMap(SystemUtils.k())); // CraftBukkit + private volatile IChatBaseComponent o = new ChatMessage("optimizeWorld.stage.counting"); + private static final Pattern p = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); public static final Pattern getRegionfileRegex() { return p; } // Paper - OBFHELPER +@@ -117,6 +124,11 @@ public class WorldUpgrader { + + this.o = new ChatMessage("optimizeWorld.stage.upgrading"); + ++ // Yatopia start ++ final ExecutorService upgradeExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setDaemon(true).setPriority(Thread.NORM_PRIORITY - 1).setNameFormat("WorldUpgrader Worker #%d").build()); ++ final Set> futures = new ObjectOpenHashSet<>(); ++ final com.ibm.asyncutil.locks.AsyncNamedLock regionFileLocks = com.ibm.asyncutil.locks.AsyncNamedLock.createFair(); ++ // Yatopia end + while (this.h) { + boolean flag = false; + float f1 = 0.0F; +@@ -130,6 +142,11 @@ public class WorldUpgrader { + + if (listiterator.hasNext()) { + ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) listiterator.next(); ++ // Yatopia start ++ flag = true; ++ futures.add(regionFileLocks.acquireLock(String.format("%d %d", chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()).hashCode()).toCompletableFuture().thenAcceptAsync(lockToken -> { ++ try { ++ // Yatopia end + boolean flag1 = false; + + try { +@@ -179,7 +196,12 @@ public class WorldUpgrader { + ++this.m; + } + +- flag = true; ++ // Yatopia start ++ } finally { ++ lockToken.releaseLock(); ++ } ++ }, upgradeExecutor)); ++ // Yatopia end + } + + f2 = (float) listiterator.nextIndex() / f; +@@ -192,6 +214,8 @@ public class WorldUpgrader { + } + } + ++ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // Yatopia ++ upgradeExecutor.shutdown(); // Yatopia + this.o = new ChatMessage("optimizeWorld.stage.finished"); + UnmodifiableIterator unmodifiableiterator3 = immutablemap1.values().iterator(); + +@@ -280,16 +304,19 @@ public class WorldUpgrader { + } + + public int e() { +- return this.k; ++ return (int) this.k; // Yatopia + } ++ public long getTotalChunkCount() { return this.k; } // Yatopia + + public int f() { +- return this.l; ++ return (int) this.l; // Yatopia + } ++ public long getUpgradedChunkCount() { return this.l; } // Yatopia + + public int g() { +- return this.m; ++ return (int) this.m; // Yatopia + } ++ public long getSkippedChunkCount() { return this.m; } // Yatopia + + public IChatBaseComponent h() { + return this.o; diff --git a/patches/server/0069-lithium-MultiNoiseBiomeSourceMixin.patch b/patches/server/0069-lithium-MultiNoiseBiomeSourceMixin.patch new file mode 100644 index 000000000..d3b304ed0 --- /dev/null +++ b/patches/server/0069-lithium-MultiNoiseBiomeSourceMixin.patch @@ -0,0 +1,195 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Wed, 3 Feb 2021 23:00:18 +0800 +Subject: [PATCH] lithium MultiNoiseBiomeSourceMixin + + +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +index c4fb051739c1c186c1574185e0653f513755987d..e9993fa0f3af9e3ecd01f91ef5c14e528bdb33b5 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +@@ -52,6 +52,13 @@ import org.apache.logging.log4j.Logger; + public final class BiomeBase { + + public static final Logger LOGGER = LogManager.getLogger(); ++ // Yatopia start ++ static class cProxy extends BiomeBase.c { ++ public cProxy(float f, float f1, float f2, float f3, float f4) { ++ super(f, f1, f2, f3, f4); ++ } ++ } ++ // Yatopia end + // Paper start + private static class dProxy extends BiomeBase.d { + private dProxy(Precipitation biomebase_precipitation, float f, TemperatureModifier biomebase_temperaturemodifier, float f1) { +diff --git a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerMultiNoise.java b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerMultiNoise.java +index 5287aec4c384c6cea76334ae1d8dc99070e8f43e..23637d9be9f9973266c1bba699c40cdc6c5d9e6d 100644 +--- a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerMultiNoise.java ++++ b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerMultiNoise.java +@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.Maps; + import com.mojang.datafixers.util.Either; + import com.mojang.datafixers.util.Function3; ++import com.mojang.datafixers.util.Function6; // Yatopia - decompile fix + import com.mojang.datafixers.util.Pair; + import com.mojang.serialization.Codec; + import com.mojang.serialization.DataResult; +@@ -27,26 +28,43 @@ import net.minecraft.world.level.levelgen.synth.NoiseGeneratorNormal; + + public class WorldChunkManagerMultiNoise extends WorldChunkManager { + ++ // Yatopia start - decompile fix ++ private static class aProxy extends WorldChunkManagerMultiNoise.a { ++ public aProxy(int i, List list) { ++ super(i, list); ++ } ++ } ++ private static class bProxy extends WorldChunkManagerMultiNoise.b { ++ public bProxy(MinecraftKey minecraftkey, Function3, Long, WorldChunkManagerMultiNoise> function3) { ++ super(minecraftkey, function3); ++ } ++ } ++ private static class cProxy extends WorldChunkManagerMultiNoise.c { ++ private cProxy(WorldChunkManagerMultiNoise.b worldchunkmanagermultinoise_b, IRegistry iregistry, long i) { ++ super(worldchunkmanagermultinoise_b, iregistry, i); ++ } ++ } ++ // Yatopia end + private static final WorldChunkManagerMultiNoise.a g = new WorldChunkManagerMultiNoise.a(-7, ImmutableList.of(1.0D, 1.0D)); + public static final MapCodec e = RecordCodecBuilder.mapCodec((instance) -> { + return instance.group(Codec.LONG.fieldOf("seed").forGetter((worldchunkmanagermultinoise) -> { + return worldchunkmanagermultinoise.r; + }), RecordCodecBuilder.create((instance1) -> { +- return instance1.group(BiomeBase.c.a.fieldOf("parameters").forGetter(Pair::getFirst), BiomeBase.d.fieldOf("biome").forGetter(Pair::getSecond)).apply(instance1, Pair::of); +- }).listOf().fieldOf("biomes").forGetter((worldchunkmanagermultinoise) -> { +- return worldchunkmanagermultinoise.p; +- }), WorldChunkManagerMultiNoise.a.a.fieldOf("temperature_noise").forGetter((worldchunkmanagermultinoise) -> { ++ return instance1.group(BiomeBase.cProxy.a.fieldOf("parameters").forGetter((java.util.function.Function) t -> ((Pair) t).getFirst()), BiomeBase.d.fieldOf("biome").forGetter(t1 -> (Supplier) ((Pair) t1).getSecond())).apply(instance1, Pair::of); // Yatopia - decompile fix ++ }).listOf().fieldOf("biomes").forGetter((Function) worldchunkmanagermultinoise -> { // Yatopia - decompile fix ++ return ((WorldChunkManagerMultiNoise) worldchunkmanagermultinoise).p; // Yatopia - decompile fix ++ }), WorldChunkManagerMultiNoise.aProxy.a.fieldOf("temperature_noise").forGetter((worldchunkmanagermultinoise) -> { // Yatopia - decompile fix + return worldchunkmanagermultinoise.h; +- }), WorldChunkManagerMultiNoise.a.a.fieldOf("humidity_noise").forGetter((worldchunkmanagermultinoise) -> { ++ }), WorldChunkManagerMultiNoise.aProxy.a.fieldOf("humidity_noise").forGetter((worldchunkmanagermultinoise) -> { // Yatopia - decompile fix + return worldchunkmanagermultinoise.i; +- }), WorldChunkManagerMultiNoise.a.a.fieldOf("altitude_noise").forGetter((worldchunkmanagermultinoise) -> { ++ }), WorldChunkManagerMultiNoise.aProxy.a.fieldOf("altitude_noise").forGetter((worldchunkmanagermultinoise) -> { // Yatopia - decompile fix + return worldchunkmanagermultinoise.j; +- }), WorldChunkManagerMultiNoise.a.a.fieldOf("weirdness_noise").forGetter((worldchunkmanagermultinoise) -> { ++ }), WorldChunkManagerMultiNoise.aProxy.a.fieldOf("weirdness_noise").forGetter((worldchunkmanagermultinoise) -> { // Yatopia - decompile fix + return worldchunkmanagermultinoise.k; +- })).apply(instance, WorldChunkManagerMultiNoise::new); ++ })).apply(instance, (Function6>>, WorldChunkManagerMultiNoise.a, WorldChunkManagerMultiNoise.a, WorldChunkManagerMultiNoise.a, WorldChunkManagerMultiNoise.a, WorldChunkManagerMultiNoise>) WorldChunkManagerMultiNoise::new); // Yatopia - decompile fix + }); +- public static final Codec f = Codec.mapEither(WorldChunkManagerMultiNoise.c.a, WorldChunkManagerMultiNoise.e).xmap((either) -> { +- return (WorldChunkManagerMultiNoise) either.map(WorldChunkManagerMultiNoise.c::d, Function.identity()); ++ public static final Codec f = Codec.mapEither(WorldChunkManagerMultiNoise.cProxy.a, WorldChunkManagerMultiNoise.e).xmap((either) -> { // Yatopia - decompile fix ++ return (WorldChunkManagerMultiNoise) either.map(c -> c.d(), Function.identity()); // Yatopia - decompile fix + }, (worldchunkmanagermultinoise) -> { + return (Either) worldchunkmanagermultinoise.d().map(Either::left).orElseGet(() -> { + return Either.right(worldchunkmanagermultinoise); +@@ -100,8 +118,44 @@ public class WorldChunkManagerMultiNoise extends WorldChunkManager { + }); + } + ++ //Yatopia - Faster Method ++ /** ++ * @reason Remove stream based code in favor of regular collections. ++ * @author SuperCoder79 ++ */ + @Override + public BiomeBase getBiome(int i, int j, int k) { ++ // [VanillaCopy] MultiNoiseBiomeSource#getBiomeForNoiseGen ++ ++ // Get the y value for perlin noise sampling. This field is always set to false in vanilla code. ++ int l = this.q ? j : 0; ++ ++ // Calculate the noise point based using 4 perlin noise samplers. ++ BiomeBase.c biomebase_c = new BiomeBase.c( ++ (float) this.l.a(i, l, k), ++ (float) this.m.a(i, l, k), ++ (float) this.n.a(i, l, k), ++ (float) this.o.a(i, l, k), ++ 0.0F); ++ ++ int idx = -1; ++ float min = Float.POSITIVE_INFINITY; ++ ++ // Iterate through the biome points and calculate the distance to the current noise point. ++ for (int itterator = 0; itterator < this.p.size(); itterator++) { ++ float distance = this.p.get(itterator).getFirst().a(biomebase_c); ++ ++ // If the distance is less than the recorded minimum, update the minimum and set the current index. ++ if (min > distance) { ++ idx = itterator; ++ min = distance; ++ } ++ } ++ ++ // Return the biome with the noise point closest to the evaluated one. ++ return this.p.get(idx).getSecond().get() == null ? BiomeRegistry.b : this.p.get(idx).getSecond().get(); ++ } ++ /* //Yatopia - Replace Method + int l = this.q ? j : 0; + BiomeBase.c biomebase_c = new BiomeBase.c((float) this.l.a((double) i, (double) l, (double) k), (float) this.m.a((double) i, (double) l, (double) k), (float) this.n.a((double) i, (double) l, (double) k), (float) this.o.a((double) i, (double) l, (double) k), 0.0F); + +@@ -109,15 +163,17 @@ public class WorldChunkManagerMultiNoise extends WorldChunkManager { + return ((BiomeBase.c) pair.getFirst()).a(biomebase_c); + })).map(Pair::getSecond).map(Supplier::get).orElse(BiomeRegistry.b); + } ++ */ //Yatopia End ++ + + public boolean b(long i) { +- return this.r == i && this.s.isPresent() && Objects.equals(((Pair) this.s.get()).getSecond(), WorldChunkManagerMultiNoise.b.a); ++ return this.r == i && this.s.isPresent() && Objects.equals(((Pair) this.s.get()).getSecond(), WorldChunkManagerMultiNoise.bProxy.a); // Yatopia - decompile fix + } + + public static class b { + +- private static final Map b = Maps.newHashMap(); +- public static final WorldChunkManagerMultiNoise.b a = new WorldChunkManagerMultiNoise.b(new MinecraftKey("nether"), (worldchunkmanagermultinoise_b, iregistry, olong) -> { ++ protected static final Map b = Maps.newHashMap(); // Yatopia - decompile fix ++ public static final WorldChunkManagerMultiNoise.b a = new WorldChunkManagerMultiNoise.b(new MinecraftKey("nether"), (worldchunkmanagermultinoise_b, iregistry, olong) -> { // Yatopia - decompile fix + return new WorldChunkManagerMultiNoise(olong, ImmutableList.of(Pair.of(new BiomeBase.c(0.0F, 0.0F, 0.0F, 0.0F, 0.0F), () -> { + return (BiomeBase) iregistry.d(Biomes.NETHER_WASTES); + }), Pair.of(new BiomeBase.c(0.0F, -0.5F, 0.0F, 0.0F, 0.0F), () -> { +@@ -136,7 +192,7 @@ public class WorldChunkManagerMultiNoise extends WorldChunkManager { + public b(MinecraftKey minecraftkey, Function3, Long, WorldChunkManagerMultiNoise> function3) { + this.c = minecraftkey; + this.d = function3; +- WorldChunkManagerMultiNoise.b.b.put(minecraftkey, this); ++ WorldChunkManagerMultiNoise.bProxy.b.put(minecraftkey, this); // Yatopia - decompile fix + } + + public WorldChunkManagerMultiNoise a(IRegistry iregistry, long i) { +@@ -144,16 +200,16 @@ public class WorldChunkManagerMultiNoise extends WorldChunkManager { + } + } + +- static final class c { ++ static class c { // Yatopia - decompile fix + + public static final MapCodec a = RecordCodecBuilder.mapCodec((instance) -> { + return instance.group(MinecraftKey.a.flatXmap((minecraftkey) -> { +- return (DataResult) Optional.ofNullable(WorldChunkManagerMultiNoise.b.b.get(minecraftkey)).map(DataResult::success).orElseGet(() -> { ++ return (DataResult) Optional.ofNullable(bProxy.b.get(minecraftkey)).map(DataResult::success).orElseGet(() -> { // Yatopia - decompile fix + return DataResult.error("Unknown preset: " + minecraftkey); + }); + }, (worldchunkmanagermultinoise_b) -> { +- return DataResult.success(worldchunkmanagermultinoise_b.c); +- }).fieldOf("preset").stable().forGetter(WorldChunkManagerMultiNoise.c::a), RegistryLookupCodec.a(IRegistry.ay).forGetter(WorldChunkManagerMultiNoise.c::b), Codec.LONG.fieldOf("seed").stable().forGetter(WorldChunkManagerMultiNoise.c::c)).apply(instance, instance.stable(WorldChunkManagerMultiNoise.c::new)); ++ return DataResult.success(((WorldChunkManagerMultiNoise.b) worldchunkmanagermultinoise_b).c); // Yatopia - decompile fix ++ }).fieldOf("preset").stable().forGetter(o -> ((WorldChunkManagerMultiNoise.c) o).a()), (RecordCodecBuilder) RegistryLookupCodec.a(IRegistry.ay).forGetter((Function>) c -> c.b()), (RecordCodecBuilder) Codec.LONG.fieldOf("seed").stable().forGetter((Function) c -> c.c())).apply(instance, instance.stable((Function3, Long, WorldChunkManagerMultiNoise.c>) WorldChunkManagerMultiNoise.c::new)); // Yatopia - decompile fix + }); + private final WorldChunkManagerMultiNoise.b b; + private final IRegistry c; +@@ -187,7 +243,7 @@ public class WorldChunkManagerMultiNoise extends WorldChunkManager { + private final int b; + private final DoubleList c; + public static final Codec a = RecordCodecBuilder.create((instance) -> { +- return instance.group(Codec.INT.fieldOf("firstOctave").forGetter(WorldChunkManagerMultiNoise.a::a), Codec.DOUBLE.listOf().fieldOf("amplitudes").forGetter(WorldChunkManagerMultiNoise.a::b)).apply(instance, WorldChunkManagerMultiNoise.a::new); ++ return instance.group(Codec.INT.fieldOf("firstOctave").forGetter((Function) a -> a.a()), Codec.DOUBLE.listOf().fieldOf("amplitudes").forGetter(a -> a.b())).apply(instance, WorldChunkManagerMultiNoise.a::new); // Yatopia - decompile fix + }); + + public a(int i, List list) { diff --git a/patches/server/0070-Force-world-save.patch b/patches/server/0070-Force-world-save.patch new file mode 100644 index 000000000..8fdece00e --- /dev/null +++ b/patches/server/0070-Force-world-save.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Tue, 9 Feb 2021 21:42:09 +0800 +Subject: [PATCH] Force world save + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +index 29cd71efe86eea2227f373c15c39dc530e9e8199..4028526166fe528d772729e9334289df460f3b1e 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +@@ -366,6 +366,7 @@ public class VillagePlace extends RegionFileSection { + }).orElse(false); + } + ++ private final java.util.concurrent.atomic.AtomicBoolean hasWorked = new java.util.concurrent.atomic.AtomicBoolean(false); // Yatopia - enforce one chunk unload per tick + @Override + public void a(BooleanSupplier booleansupplier) { + // Paper start - async chunk io +@@ -373,7 +374,7 @@ public class VillagePlace extends RegionFileSection { + super.a(booleansupplier); + } else { + //super.a(booleansupplier); // re-implement below +- while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean() && !this.world.isSavingDisabled()) { // Tuinity - unload POI data - don't write to disk if saving is disabled ++ hasWorked.set(false); while (!((RegionFileSection)this).d.isEmpty() && (hasWorked.compareAndSet(false, true) || booleansupplier.getAsBoolean()) && !this.world.isSavingDisabled()) { // Tuinity - unload POI data - don't write to disk if saving is disabled // Yatopia - enforce one chunk unload per tick + ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).r(); + + NBTTagCompound data; +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +index f70c14385c95763b5f270a6e2ce372cf047ba7bb..4454489153f7932deaaebe8aa32ecb603764f42b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +@@ -50,8 +50,9 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + //this.b = new IOWorker(file, flag, file.getName()); // Paper - nuke IOWorker + } + ++ private final java.util.concurrent.atomic.AtomicBoolean hasWorked = new java.util.concurrent.atomic.AtomicBoolean(false); // Yatopia - enforce one chunk unload per tick + protected void a(BooleanSupplier booleansupplier) { +- while (!this.d.isEmpty() && booleansupplier.getAsBoolean()) { ++ hasWorked.set(false); while (!this.d.isEmpty() && (hasWorked.compareAndSet(false, true) || booleansupplier.getAsBoolean())) { // Yatopia - enforce one chunk unload per tick + ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(this.d.firstLong()).r(); // Paper - conflict here to avoid obfhelpers + + this.d(chunkcoordintpair); diff --git a/patches/server/0071-java-16.patch b/patches/server/0071-java-16.patch new file mode 100644 index 000000000..b704f7748 --- /dev/null +++ b/patches/server/0071-java-16.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Fri, 23 Apr 2021 11:11:20 -0400 +Subject: [PATCH] java 16 + + +diff --git a/pom.xml b/pom.xml +index e8884c5f79f0550dd479074f1b69e8b9f7b68784..620bd31a4356cb170720b4be3512791c93069131 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -14,8 +14,8 @@ + git + 1.16.5 + 1_16_R3 +- 1.8 +- 1.8 ++ 16 ++ 16 + + + diff --git a/patches/server/0072-C2ME-update.patch b/patches/server/0072-C2ME-update.patch new file mode 100644 index 000000000..3b06c2115 --- /dev/null +++ b/patches/server/0072-C2ME-update.patch @@ -0,0 +1,425 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Thu, 27 May 2021 14:11:39 -0400 +Subject: [PATCH] C2ME update + + +diff --git a/pom.xml b/pom.xml +index 620bd31a4356cb170720b4be3512791c93069131..a57144d43adbf4a9b97d168fea3bc09fa41a96d8 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -236,6 +236,11 @@ + asyncutil + 0.1.0 + ++ ++ org.threadly ++ threadly ++ 6.6 ++ + + + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index e24f323561875c1ef313dd5ceb222f9a2a1251d3..da28d5fc737eb36fe24cd48e762eb9fab6e32110 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -110,6 +110,9 @@ import org.apache.logging.log4j.Logger; + + import org.bukkit.entity.Player; // CraftBukkit + import org.spigotmc.AsyncCatcher; ++import net.minecraft.world.level.biome.WorldChunkManagerOverworld; // Yatopia ++import net.minecraft.world.level.chunk.BiomeStorage; ++import org.yatopiamc.c2me.common.optimization.worldgen.global_biome_cache.BiomeCache; // Yatopia + + public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + +@@ -1208,11 +1211,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + } + +- public CompletableFuture> a(PlayerChunk playerchunk, ChunkStatus chunkstatus) { ++ public CompletableFuture> a(PlayerChunk playerchunk, ChunkStatus chunkstatus) { // yarn: getChunk + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + + if (chunkstatus == ChunkStatus.EMPTY) { +- return this.f(chunkcoordintpair); ++ // Yatopia start - port C2ME ++ // return this.f(chunkcoordintpair); ++ return this.loadChunk(chunkcoordintpair).thenApplyAsync(either -> { ++ if (chunkGenerator.getWorldChunkManager() instanceof WorldChunkManagerOverworld source) { ++ either.left().ifPresent(chunk -> { ++ final BiomeStorage biomeArray = source.preloadBiomes(chunkcoordintpair, chunk.getBiomeIndex()); ++ if (chunk instanceof ProtoChunk protoChunk) protoChunk.setBiomes(biomeArray); ++ }); ++ } ++ return either; ++ }, BiomeCache.EXECUTOR); ++ // Yatopia end + } else { + CompletableFuture> completablefuture = playerchunk.a(chunkstatus.e(), this); + +@@ -1249,6 +1263,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + } + ++ private CompletableFuture> loadChunk(ChunkCoordIntPair chunkcoordintpair) { return this.f(chunkcoordintpair); } // Yatopia - OBFHELPER + private CompletableFuture> f(ChunkCoordIntPair chunkcoordintpair) { + // Paper start - Async chunk io + final java.util.function.BiFunction> syncLoadComplete = (chunkHolder, ioThrowable) -> { +diff --git a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerOverworld.java b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerOverworld.java +index 28b2c69ef1ad7938b09dd39e34008956f4922483..eb41c9524332d89d09df7e0fc91dd0310ad16067 100644 +--- a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerOverworld.java ++++ b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerOverworld.java +@@ -10,9 +10,14 @@ import net.minecraft.resources.RegistryLookupCodec; + import net.minecraft.resources.ResourceKey; + import net.minecraft.world.level.newbiome.layer.GenLayer; + import net.minecraft.world.level.newbiome.layer.GenLayers; ++import org.yatopiamc.c2me.common.optimization.worldgen.global_biome_cache.BiomeCache; // Yatopia ++import net.minecraft.world.level.ChunkCoordIntPair; // Yatopia ++import net.minecraft.world.level.chunk.BiomeStorage; // Yatopia + + public class WorldChunkManagerOverworld extends WorldChunkManager { + ++ private BiomeCache cacheImpl = null; // Yatopia - port C2ME ++ + public static final Codec e = RecordCodecBuilder.create((instance) -> { + return instance.group(Codec.LONG.fieldOf("seed").stable().forGetter((worldchunkmanageroverworld) -> { + return worldchunkmanageroverworld.h; +@@ -24,12 +29,18 @@ public class WorldChunkManagerOverworld extends WorldChunkManager { + return worldchunkmanageroverworld.k; + })).apply(instance, instance.stable(WorldChunkManagerOverworld::new)); + }); +- private final GenLayer f; ++ public static final Codec getCodec() { return e; } // Yatopia - OBFHELPER ++ private final GenLayer f; // yarn: biomeSampler ++ private final GenLayer getBiomeSampler() { return this.f; } // Yatopia - OBFHELPER + private static final List> g = ImmutableList.of(Biomes.OCEAN, Biomes.PLAINS, Biomes.DESERT, Biomes.MOUNTAINS, Biomes.FOREST, Biomes.TAIGA, Biomes.SWAMP, Biomes.RIVER, Biomes.FROZEN_OCEAN, Biomes.FROZEN_RIVER, Biomes.SNOWY_TUNDRA, Biomes.SNOWY_MOUNTAINS, new ResourceKey[]{Biomes.MUSHROOM_FIELDS, Biomes.MUSHROOM_FIELD_SHORE, Biomes.BEACH, Biomes.DESERT_HILLS, Biomes.WOODED_HILLS, Biomes.TAIGA_HILLS, Biomes.MOUNTAIN_EDGE, Biomes.JUNGLE, Biomes.JUNGLE_HILLS, Biomes.JUNGLE_EDGE, Biomes.DEEP_OCEAN, Biomes.STONE_SHORE, Biomes.SNOWY_BEACH, Biomes.BIRCH_FOREST, Biomes.BIRCH_FOREST_HILLS, Biomes.DARK_FOREST, Biomes.SNOWY_TAIGA, Biomes.SNOWY_TAIGA_HILLS, Biomes.GIANT_TREE_TAIGA, Biomes.GIANT_TREE_TAIGA_HILLS, Biomes.WOODED_MOUNTAINS, Biomes.SAVANNA, Biomes.SAVANNA_PLATEAU, Biomes.BADLANDS, Biomes.WOODED_BADLANDS_PLATEAU, Biomes.BADLANDS_PLATEAU, Biomes.WARM_OCEAN, Biomes.LUKEWARM_OCEAN, Biomes.COLD_OCEAN, Biomes.DEEP_WARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN, Biomes.DEEP_COLD_OCEAN, Biomes.DEEP_FROZEN_OCEAN, Biomes.SUNFLOWER_PLAINS, Biomes.DESERT_LAKES, Biomes.GRAVELLY_MOUNTAINS, Biomes.FLOWER_FOREST, Biomes.TAIGA_MOUNTAINS, Biomes.SWAMP_HILLS, Biomes.ICE_SPIKES, Biomes.MODIFIED_JUNGLE, Biomes.MODIFIED_JUNGLE_EDGE, Biomes.TALL_BIRCH_FOREST, Biomes.TALL_BIRCH_HILLS, Biomes.DARK_FOREST_HILLS, Biomes.SNOWY_TAIGA_MOUNTAINS, Biomes.GIANT_SPRUCE_TAIGA, Biomes.GIANT_SPRUCE_TAIGA_HILLS, Biomes.MODIFIED_GRAVELLY_MOUNTAINS, Biomes.SHATTERED_SAVANNA, Biomes.SHATTERED_SAVANNA_PLATEAU, Biomes.ERODED_BADLANDS, Biomes.MODIFIED_WOODED_BADLANDS_PLATEAU, Biomes.MODIFIED_BADLANDS_PLATEAU}); ++ private static final List getBiomes() { return biomes; } // Yatopia - OBHELPER + private final long h; + private final boolean i; + private final boolean j; +- private final IRegistry k; ++ private final IRegistry k; // yarn: biomeRegistry ++ private final IRegistry getBiomeRegistry() { return this.k; } // Yatopia - OBFHELPER ++ ++ private static List biomes = null; // Yatopia + + public WorldChunkManagerOverworld(long i, boolean flag, boolean flag1, IRegistry iregistry) { + super(WorldChunkManagerOverworld.g.stream().map((resourcekey) -> { +@@ -42,7 +53,17 @@ public class WorldChunkManagerOverworld extends WorldChunkManager { + this.j = flag1; + this.k = iregistry; + this.f = GenLayers.a(i, flag, flag1 ? 6 : 4, 4); +- } ++ // Yatopia start - port C2ME ++ if (this.biomes == null) { ++ List biomes = new java.util.ArrayList<>(); ++ for (ResourceKey resourceKey : g) { ++ biomes.add((BiomeBase) iregistry.d(resourceKey)); ++ } ++ this.biomes = biomes; ++ } ++ this.cacheImpl = new BiomeCache(this.getBiomeSampler(), this.getBiomeRegistry(), getBiomes()); ++ // Yatopia end ++ } + + @Override + protected Codec a() { +@@ -51,6 +72,16 @@ public class WorldChunkManagerOverworld extends WorldChunkManager { + + @Override + public BiomeBase getBiome(int i, int j, int k) { +- return this.f.a(this.k, i, k); ++ return this.cacheImpl.getBiomeForNoiseGen(i, j, k); // Yatopia - port C2ME ++ } ++ ++ // Yatopia start - port C2ME ++ public BiomeStorage preloadBiomes(ChunkCoordIntPair pos, BiomeStorage def) { ++ return cacheImpl.preloadBiomes(pos, def); ++ } ++ ++ public BiomeStorage getBiomes(ChunkCoordIntPair pos) { ++ return cacheImpl.preloadBiomes(pos, null); + } ++ // Yatopia end + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index ff98335155c86803b98d8c67f0b40b8d65214890..11654e1fa2d68026280b6805621a0597e2899038 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -47,12 +47,13 @@ import net.minecraft.world.level.levelgen.feature.configurations.StructureSettin + import net.minecraft.world.level.levelgen.feature.configurations.StructureSettingsStronghold; + import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureManager; ++import net.minecraft.world.level.biome.WorldChunkManagerOverworld; // Yatopia + + public abstract class ChunkGenerator { + + public static final Codec a; + protected final WorldChunkManager b; +- protected final WorldChunkManager c; ++ protected final WorldChunkManager c; // Yarn: biomeSource + private final StructureSettings structureSettings; + private final long e; + private final List f; +@@ -126,8 +127,14 @@ public abstract class ChunkGenerator { + + public void createBiomes(IRegistry iregistry, IChunkAccess ichunkaccess) { + ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos(); ++ // Yatopia start - port C2ME ++ if (this.getWorldChunkManager() instanceof WorldChunkManagerOverworld) { ++ ((ProtoChunk) ichunkaccess).setBiomes(((WorldChunkManagerOverworld) this.getWorldChunkManager()).preloadBiomes(chunkcoordintpair, ichunkaccess.getBiomeIndex())); ++ return; ++ } + + ((ProtoChunk) ichunkaccess).a(new BiomeStorage(iregistry, chunkcoordintpair, this.c)); ++ // Yatopia end + } + + public void doCarving(long i, BiomeManager biomemanager, IChunkAccess ichunkaccess, WorldGenStage.Features worldgenstage_features) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index 1802498d48493d3e63c999a067c71e65ea29a890..2ba95a5a2649d2b8d3d3a64e5094ca52d63c7482 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -231,7 +231,7 @@ public class ChunkStatus { + if ((Object) this == ChunkStatus.LIGHT) { + this.reducedTaskRadius = 1; + } +- System.out.println(String.format("%s task radius: %d -> %d", this, this.getNeighborRadius(), this.reducedTaskRadius)); ++ System.out.printf("%s task radius: %d -> %d%n", this, this.getNeighborRadius(), this.reducedTaskRadius); + } + // Yatopia end + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index 1658f0bb379653c205d08c771a7c23242d50f66d..e1bd44b4c9d0cb32a904dd6dc8bee1a9da7c0a99 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -327,6 +327,7 @@ public class ProtoChunk implements IChunkAccess { + return this.k; + } + ++ public void setBiomes(BiomeStorage biomestorage) { a(biomestorage); } // Yatopia - OBFHELPER + public void a(BiomeStorage biomestorage) { + this.d = biomestorage; + } +diff --git a/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java b/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java +index 8b61bce3d7587832ddb2c0e29e76f9c68ddf5d8b..a8fd0589cdd3e68f95740f429eec8c2535b6b3a9 100644 +--- a/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java ++++ b/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java +@@ -20,6 +20,7 @@ public class GenLayer { + this.b = (AreaLazy) areafactory.make(); + } + ++ public BiomeBase sample(IRegistry iregistry, int i, int j) { return this.a(iregistry, i, j); } // Yatopia - OBFHELPER + public BiomeBase a(IRegistry iregistry, int i, int j) { + int k = this.b.a(i, j); + ResourceKey resourcekey = BiomeRegistry.a(k); +diff --git a/src/main/java/org/yatopiamc/c2me/common/optimization/package-info.java b/src/main/java/org/yatopiamc/c2me/common/optimization/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..037fea071962d35ddc70e7ef95a82b58329eca03 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/optimization/package-info.java +@@ -0,0 +1 @@ ++package org.yatopiamc.c2me.common.optimization; +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/optimization/worldgen/global_biome_cache/BiomeCache.java b/src/main/java/org/yatopiamc/c2me/common/optimization/worldgen/global_biome_cache/BiomeCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4dfc76ca456b5db1ec1c512f02df04ae3e7a6bea +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/optimization/worldgen/global_biome_cache/BiomeCache.java +@@ -0,0 +1,60 @@ ++package org.yatopiamc.c2me.common.optimization.worldgen.global_biome_cache; ++ ++import com.google.common.cache.CacheBuilder; ++import com.google.common.cache.CacheLoader; ++import com.google.common.cache.LoadingCache; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++ ++import org.threadly.concurrent.UnfairExecutor; ++ ++import java.util.List; ++import java.util.WeakHashMap; ++ ++import net.minecraft.world.level.chunk.BiomeStorage; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.core.IRegistry; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.newbiome.layer.GenLayer; ++ ++public class BiomeCache { ++ ++ public static final UnfairExecutor EXECUTOR = new UnfairExecutor(2, new ThreadFactoryBuilder().setNameFormat("C2ME biomes #%d").setDaemon(true).setPriority(Thread.NORM_PRIORITY - 1).build()); ++ ++ private final IRegistry registry; ++ ++ private final UncachedBiomeSource uncachedBiomeSource; ++ ++ public BiomeCache(GenLayer sampler, IRegistry registry, List biomes) { ++ this.registry = registry; ++ this.uncachedBiomeSource = new UncachedBiomeSource(biomes, sampler, registry); ++ } ++ ++ private final LoadingCache biomeCache = CacheBuilder.newBuilder() ++ .softValues() ++ .maximumSize(8192) ++ .build(new CacheLoader<>() { ++ @Override ++ public BiomeStorage load(ChunkCoordIntPair key) { ++ return new BiomeStorage(registry, key, uncachedBiomeSource); ++ } ++ }); ++ ++ private final ThreadLocal> threadLocalCache = ThreadLocal.withInitial(WeakHashMap::new); ++ ++ public BiomeBase getBiomeForNoiseGen(int biomeX, int biomeY, int biomeZ) { ++ final ChunkCoordIntPair chunkcoordinitpair = new ChunkCoordIntPair(biomeX >> 2, biomeZ >> 2); ++ final int startX = chunkcoordinitpair.getBlockX() >> 2; ++ final int startZ = chunkcoordinitpair.getBlockZ() >> 2; ++ return threadLocalCache.get().computeIfAbsent(chunkcoordinitpair, biomeCache).getBiome(biomeX - startX, biomeY, biomeZ - startZ); ++ } ++ ++ public BiomeStorage preloadBiomes(ChunkCoordIntPair pos, BiomeStorage def) { ++ if (def != null) { ++ biomeCache.put(pos, def); ++ return def; ++ } else { ++ return biomeCache.getUnchecked(pos); ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/optimization/worldgen/global_biome_cache/UncachedBiomeSource.java b/src/main/java/org/yatopiamc/c2me/common/optimization/worldgen/global_biome_cache/UncachedBiomeSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ea2bf5dc62d2dceb94593152e3d9060ae51eb32f +--- /dev/null ++++ b/src/main/java/org/yatopiamc/c2me/common/optimization/worldgen/global_biome_cache/UncachedBiomeSource.java +@@ -0,0 +1,39 @@ ++package org.yatopiamc.c2me.common.optimization.worldgen.global_biome_cache; ++ ++import com.mojang.serialization.Codec; ++import java.util.List; ++ ++import net.minecraft.world.level.biome.WorldChunkManager; ++import net.minecraft.world.level.newbiome.layer.GenLayer; ++import net.minecraft.core.IRegistry; ++import net.minecraft.world.level.biome.BiomeBase; ++ ++import net.minecraft.world.level.biome.WorldChunkManagerOverworld; ++ ++public class UncachedBiomeSource extends WorldChunkManager { ++ private final GenLayer sampler; ++ private final IRegistry registry; ++ ++ public UncachedBiomeSource(List biomes, GenLayer sampler, IRegistry registry) { ++ super(biomes); ++ this.sampler = sampler; ++ this.registry = registry; ++ } ++ ++ protected Codec getCodec() { return this.a(); } ++ ++ @Override ++ protected Codec a() { // getCodec ++ return WorldChunkManagerOverworld.getCodec(); ++ } ++ ++ // @Override ++ // public WorldChunkManager withSeed(long seed) { ++ // throw new UnsupportedOperationException(); ++ // } ++ ++ @Override ++ public BiomeBase getBiome(int biomeX, int biomeY, int biomeZ) { ++ return sampler.sample(this.registry, biomeX, biomeZ); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java b/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java +index 9e34b74d9abecae4b386d49514ceb0d1f333e271..bd19b5f16ca339ae634dbc0aba5d226b0daa2256 100644 +--- a/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java ++++ b/src/main/java/org/yatopiamc/c2me/common/util/AsyncCombinedLock.java +@@ -1,10 +1,11 @@ + package org.yatopiamc.c2me.common.util; + +-import com.google.common.collect.Sets; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; + import com.ibm.asyncutil.locks.AsyncLock; + import com.ibm.asyncutil.locks.AsyncNamedLock; +-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + import net.minecraft.world.level.ChunkCoordIntPair; ++import org.threadly.concurrent.UnfairExecutor; ++import org.yatopiamc.yatopia.server.YatopiaConfig; + + import java.util.Optional; + import java.util.Set; +@@ -15,11 +16,9 @@ import java.util.stream.Collectors; + + public class AsyncCombinedLock { + +- public static final ForkJoinPool lockWorker = new ForkJoinPool( +- 2, +- new C2MEForkJoinWorkerThreadFactory("C2ME lock worker #%d", Thread.NORM_PRIORITY - 1), +- null, +- true ++ public static final UnfairExecutor lockWorker = new UnfairExecutor( ++ YatopiaConfig.c2meThreads, ++ new ThreadFactoryBuilder().setDaemon(true).setPriority(Thread.NORM_PRIORITY - 1).setNameFormat("C2ME lock worker #%d").build() + ); + + private final AsyncNamedLock lock; +@@ -75,14 +74,7 @@ public class AsyncCombinedLock { + return future.thenApply(Function.identity()); + } + +- @SuppressWarnings("OptionalUsedAsFieldOrParameterType") +- private static class LockEntry { +- public final ChunkCoordIntPair name; +- public final Optional lockToken; +- +- private LockEntry(ChunkCoordIntPair name, Optional lockToken) { +- this.name = name; +- this.lockToken = lockToken; +- } ++ private record LockEntry(ChunkCoordIntPair name, ++ Optional lockToken) { + } + } +diff --git a/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java b/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java +index 119421953de58fbc928e14bf618b340ee6b2fe94..6610f6990f68cac34a31e4a79be00b8d8d0d6b1d 100644 +--- a/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java ++++ b/src/main/java/org/yatopiamc/c2me/common/util/AsyncNamedLockDelegateAsyncLock.java +@@ -1,5 +1,6 @@ + package org.yatopiamc.c2me.common.util; + ++import com.google.common.base.Preconditions; + import com.ibm.asyncutil.locks.AsyncLock; + import com.ibm.asyncutil.locks.AsyncNamedLock; + +@@ -7,14 +8,11 @@ import java.util.Objects; + import java.util.Optional; + import java.util.concurrent.CompletionStage; + +-public class AsyncNamedLockDelegateAsyncLock implements AsyncLock { ++public record AsyncNamedLockDelegateAsyncLock(AsyncNamedLock delegate, ++ T name) implements AsyncLock { + +- private final AsyncNamedLock delegate; +- private final T name; +- +- public AsyncNamedLockDelegateAsyncLock(AsyncNamedLock delegate, T name) { +- this.delegate = Objects.requireNonNull(delegate); +- this.name = name; ++ public AsyncNamedLockDelegateAsyncLock { ++ Preconditions.checkNotNull(this.delegate()); + } + + @Override +@@ -26,4 +24,4 @@ public class AsyncNamedLockDelegateAsyncLock implements AsyncLock { + public Optional tryLock() { + return delegate.tryLock(name); + } +-} +\ No newline at end of file ++}