diff --git a/.github/workflows/gradleBuild.yml b/.github/workflows/gradleBuild.yml index eb936766..35891bc7 100644 --- a/.github/workflows/gradleBuild.yml +++ b/.github/workflows/gradleBuild.yml @@ -26,7 +26,7 @@ jobs: java-version: 17 - name: Build with Gradle - run: ./gradlew build -x test -x longRunTest + run: ./gradlew build -x test - name: Tar post-build state run: tar -cvzpf /tmp/post-build.tar.gz . @@ -47,15 +47,7 @@ jobs: uses: ./.github/workflows/testJob.yml with: name: 'test' - command: './gradlew check -x longRunTest' - - call-longRunTest: - needs: build - uses: ./.github/workflows/testJob.yml - with: - name: 'longRunTest' - command: './gradlew check -x test' - + command: './gradlew check' call-cleanup: - needs: [ call-test, call-longRunTest ] + needs: [ call-test ] uses: ./.github/workflows/cleanupJob.yml diff --git a/.github/workflows/gradleBuildPR.yml b/.github/workflows/gradleBuildPR.yml index 79d49cf6..a53ae2de 100644 --- a/.github/workflows/gradleBuildPR.yml +++ b/.github/workflows/gradleBuildPR.yml @@ -24,7 +24,7 @@ jobs: java-version: 17 - name: Build with Gradle - run: ./gradlew build -x test -x longRunTest + run: ./gradlew build -x test - name: Tar post-build state run: tar -cvzpf /tmp/post-build.tar.gz . @@ -39,15 +39,8 @@ jobs: uses: ./.github/workflows/testJob.yml with: name: 'test' - command: './gradlew check -x longRunTest' - - call-longRunTest: - needs: build - uses: ./.github/workflows/testJob.yml - with: - name: 'longRunTest' - command: './gradlew check -x test' + command: './gradlew check' call-cleanup: - needs: [ call-test, call-longRunTest ] + needs: [ call-test ] uses: ./.github/workflows/cleanupJob.yml diff --git a/CubicChunksCore b/CubicChunksCore index 90de799d..22d70c27 160000 --- a/CubicChunksCore +++ b/CubicChunksCore @@ -1 +1 @@ -Subproject commit 90de799d557b564bb31db3cc67b5987c09548b48 +Subproject commit 22d70c2747b7cc96fb6aba19cb5654c0835c4017 diff --git a/build.gradle b/build.gradle index 4e137a39..bd219215 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.154' + id 'net.neoforged.moddev' version '2.0.90' id("io.github.opencubicchunks.javaheaders").version("1.2.8") id("io.github.opencubicchunks.gradle.mcGitVersion") id("io.github.opencubicchunks.gradle.mixingen") @@ -71,7 +71,7 @@ mixinGen { filePattern = "cubicchunks.mixins.%s.json" defaultRefmap = "CubicChunks-refmap.json" defaultPackagePrefix = "io.github.opencubicchunks.cubicchunks.mixin" - defaultCompatibilityLevel = "JAVA_17" + defaultCompatibilityLevel = "JAVA_21" defaultMinVersion = "0.8" config(sourceSets.main, "core") { @@ -145,73 +145,98 @@ base { archivesName = mod_id } -// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. -java.toolchain.languageVersion = JavaLanguageVersion.of(17) +java.toolchain.languageVersion = JavaLanguageVersion.of(21) -minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') +neoForge { + version = project.neo_version + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + validateAccessTransformers = true + + runs { + def args = [ + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+AllowEnhancedClassRedefinition", + "-XX:-OmitStackTraceInFastThrow", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M", + "-Dmixin.debug.verbose=true", + "-Dmixin.debug.export=true", + "-Dmixin.checks.interfaces=true", + "-Dcubicchunks.debug=false", + "-Dcubicchunks.debug.loadorder=false", + "-Dcubicchunks.debug.window=false", + "-Dcubicchunks.debug.statusrenderer=false", + "-Dcubicchunks.debug.heightmaprenderer=false", + "-Dcubicchunks.debug.heightmaprenderer.server=false", + "-Dcubicchunks.debug.heightmaprenderer.render_lightmap=false", + "-Dcubicchunks.debug.heightmaprenderer.radius=2", + "-Dcubicchunks.debug.heightmapverification=false", + "-Dcubicchunks.debug.heightmapverification.frequency=1", + "-Dcubicchunks.debug.biomes=false", + "-ea" + ] + // applies to all the run configs below + configureEach { + jvmArguments = args + // systemProperty 'forge.logging.markers', 'REGISTRIES' + systemProperty 'forge.logging.console.level', 'debug' + } -configurations { - libraries {} - implementation.extendsFrom libraries - extraTests -} + client { + client() + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } -runs { - def args = [ - "-XX:+IgnoreUnrecognizedVMOptions", - "-XX:+UnlockExperimentalVMOptions", - "-XX:+AllowEnhancedClassRedefinition", - "-XX:-OmitStackTraceInFastThrow", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=32M", - "-Dmixin.debug.verbose=true", - "-Dmixin.debug.export=true", - "-Dmixin.checks.interfaces=true", - "-Dcubicchunks.debug=false", - "-Dcubicchunks.debug.loadorder=false", - "-Dcubicchunks.debug.window=false", - "-Dcubicchunks.debug.statusrenderer=false", - "-Dcubicchunks.debug.heightmaprenderer=false", - "-Dcubicchunks.debug.heightmaprenderer.server=false", - "-Dcubicchunks.debug.heightmaprenderer.render_lightmap=false", - "-Dcubicchunks.debug.heightmaprenderer.radius=2", - "-Dcubicchunks.debug.heightmapverification=false", - "-Dcubicchunks.debug.heightmapverification.frequency=1", - "-Dcubicchunks.debug.biomes=false", - "-ea" - ] - // applies to all the run configs below - configureEach { - jvmArguments args - // systemProperty 'forge.logging.markers', 'REGISTRIES' - systemProperty 'forge.logging.console.level', 'debug' - modSource project.sourceSets.forge - dependencies { - runtime project.configurations.libraries + server { + server() + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' } - } - client { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } + gameTestServer { + type = "gameTestServer" + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } - server { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - programArgument '--nogui' + clientData { + clientData() + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } } - gameTestServer { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + mods { + "${mod_id}" { + // FIXME should be forge but things don't inherit properly currently + sourceSet(sourceSets.main) + } } - data { - programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + unitTest { + // Enable JUnit support in the moddev plugin + enable() + // Configure which mod is being tested. + // This allows NeoForge to load the test/ classes and resources as belonging to the mod. + testedMod = mods.cubicchunks // must match the name in the mods { } block. + // Configure which mods are loaded in the test environment, if the default (all declared mods) is not appropriate. + // This must contain testedMod, and can include other mods as well. + // loadedMods = [mods., mods.] } } +configurations { + libraries {} + implementation.extendsFrom libraries + additionalRuntimeClasspath.extendsFrom libraries + extraTests +} + // Include resources generated by data generators. sourceSets.main.resources { srcDir 'src/generated/resources' } @@ -226,8 +251,6 @@ repositories { } dependencies { - implementation "net.neoforged:neoforge:${neo_version}" - implementation(project(":CubicChunksCore")) { attributes { attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR)) @@ -244,8 +267,6 @@ dependencies { libraries("io.github.opencubicchunks:regionlib:0.63.0-SNAPSHOT") libraries("org.spongepowered:noise:2.0.0-SNAPSHOT") - // NeoForge does not yet support JUnit tests, so we use fabric loader for this instead. - testImplementation("net.fabricmc:fabric-loader-junit:0.15.3") // required for bootstrapping in unit tests testImplementation("org.assertj:assertj-core:3.25.1") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0") @@ -254,6 +275,8 @@ dependencies { testImplementation("org.hamcrest:hamcrest-junit:2.0.0.0") testImplementation("org.hamcrest:hamcrest:2.2") + + testImplementation "net.neoforged:testframework:${neo_version}" // Needed for EphemeralTestServerProvider } tasks.withType(ProcessResources).configureEach { @@ -266,18 +289,15 @@ tasks.withType(ProcessResources).configureEach { ] inputs.properties replaceProperties - filesMatching(['META-INF/mods.toml']) { + filesMatching(['META-INF/neoforge.mods.toml']) { expand replaceProperties + [project: project] } } // Force various tasks to run on intellij refresh -project.tasks.matching { - // Using idePostSync instead of ideaSyncTask as it was removed - it.name == "idePostSync" -}.forEach { - it.dependsOn("CubicChunksCore:assemble") - it.dependsOn(genAll) +neoForge { + ideSyncTask project("CubicChunksCore").getTasksByName("assemble", false)[0] + ideSyncTask genAll } // Ensure we always genAll on any run processResources.dependsOn(genAll, "CubicChunksCore:assemble") @@ -286,20 +306,21 @@ processResources.dependsOn(genAll, "CubicChunksCore:assemble") compileJava.dependsOn("CubicChunksCore:assemble") // unzipping subproject (CubicChunksCore) tests -tasks.register('unzipTests', Copy) { - outputs.upToDateWhen { - false - } - dependsOn(configurations.named("extraTests")) - from(configurations.named("extraTests")) - doFirst { - //noinspection ConfigurationAvoidance, we actually do want to resolve here - var testsFile = configurations["extraTests"].resolve().iterator().next() - from(zipTree(testsFile)) - exclude(testsFile.name) - } - into(sourceSets.test.output.classesDirs.asPath) -} +// TODO disabled until we figure out how to fix java modules complaining +//tasks.register('unzipTests', Copy) { +// outputs.upToDateWhen { +// false +// } +// dependsOn(configurations.named("extraTests")) +// from(configurations.named("extraTests")) +// doFirst { +// //noinspection ConfigurationAvoidance, we actually do want to resolve here +// var testsFile = configurations["extraTests"].resolve().iterator().next() +// from(zipTree(testsFile)) +// exclude(testsFile.name) +// } +// into(sourceSets.test.output.classesDirs.asPath) +//} tasks.withType(Test).configureEach { // gradle docs suggest processors/2 which actually runs fractionally faster than just processors @@ -309,11 +330,9 @@ tasks.withType(Test).configureEach { test { minHeapSize = "512M" - // TODO IntegrationTestServerCubeCache.testAddCubicRegionTicket currently requires ~4GB of memory; - // we should reduce maxHeapSize to a more reasonable value once chunkloading memory usage is further optimized - maxHeapSize = "6G" + maxHeapSize = "2048M" - dependsOn(project.tasks.named("unzipTests")) +// dependsOn(project.tasks.named("unzipTests")) useJUnitPlatform { excludeTags "longRunTest" } @@ -330,34 +349,36 @@ test { exclude "**/mixin*" } -def longRunTest = tasks.register("longRunTest", Test) { - minHeapSize = "512M" - maxHeapSize = "2048M" - systemProperty("junit.jupiter.testclass.order.default", "org.junit.jupiter.api.ClassOrderer.OrderAnnotation") - - useJUnitPlatform { - includeTags "longRunTest" - } - - // Show test results. - testLogging { - events("started", "passed", "skipped", "failed") - showStandardStreams = true - exceptionFormat = TestExceptionFormat.FULL - minGranularity = 3 - } - - jvmArgs("-ea", "-Dmixin.debug.verbose=true", "-Dmixin.debug.export=true", "-Dmixin.checks.interfaces=true") - exclude "**/mixin*" - - group = "Verification" - mustRunAfter project.tasks.named("unzipTests") - shouldRunAfter project.tasks.named("test") -} - -tasks.named("check") { - dependsOn longRunTest -} +// TODO longRunTest is disabled for now because MDG doesn't support multiple test tasks +// we don't currently need it anyway, as all tests currently complete reasonably quickly. +//def longRunTest = tasks.register("longRunTest", Test) { +// minHeapSize = "512M" +// maxHeapSize = "2048M" +// systemProperty("junit.jupiter.testclass.order.default", "org.junit.jupiter.api.ClassOrderer.OrderAnnotation") +// +// useJUnitPlatform { +// includeTags "longRunTest" +// } +// +// // Show test results. +// testLogging { +// events("started", "passed", "skipped", "failed") +// showStandardStreams = true +// exceptionFormat = TestExceptionFormat.FULL +// minGranularity = 3 +// } +// +// jvmArgs("-ea", "-Dmixin.debug.verbose=true", "-Dmixin.debug.export=true", "-Dmixin.checks.interfaces=true") +// exclude "**/mixin*" +// +// group = "Verification" +//// mustRunAfter project.tasks.named("unzipTests") +// shouldRunAfter project.tasks.named("test") +//} +// +//tasks.named("check") { +// dependsOn longRunTest +//} publishing { publications { diff --git a/gradle.properties b/gradle.properties index 5165caf3..abfb9d20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,20 +6,20 @@ org.gradle.debug=false ## Environment Properties # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge # The Minecraft version must agree with the Neo version to get a valid artifact -minecraft_version=1.20.4 +minecraft_version=1.21.5 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.20.4,1.21) +minecraft_version_range=[1.21.5,1.21.6) # The Neo version must agree with the Minecraft version to get a valid artifact -neo_version=20.4.248 +neo_version=21.5.75 # The Neo version range can use any version of Neo as bounds -neo_version_range=[20.4,) +neo_version_range=[21.5,) # The loader version range can only use the major version of FML as bounds -loader_version_range=[2,) +loader_version_range=[4,) -neogradle.subsystems.parchment.minecraftVersion=1.20.3 -neogradle.subsystems.parchment.mappingsVersion=2023.12.31 +parchment_minecraft_version=1.21.5 +parchment_mappings_version=2025.04.19 ## Mod Properties diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a..002b867c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 91f1c7ab..5cb18597 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,6 @@ pluginManagement { } plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' } include ':CubicChunksCore' \ No newline at end of file diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/ConstructorSuper.java b/src/main/java/io/github/opencubicchunks/cubicchunks/ConstructorSuper.java deleted file mode 100644 index 3d2811b0..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/ConstructorSuper.java +++ /dev/null @@ -1,126 +0,0 @@ -package io.github.opencubicchunks.cubicchunks; - -import org.apache.commons.lang3.NotImplementedException; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.*; -import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.InjectionPoint.AtCode; -import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; -import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; -import org.spongepowered.asm.mixin.transformer.MixinTargetContext; -import org.spongepowered.asm.util.Constants; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * This injection point targets a delegating {@code super()} call OR a delegating {@code this()} call - *

- * This is a temporary hack until we update to a mixin version that allows you to target CTOR_SUPER. - *

- * To use this injection point specify its fully qualified name in the @At annotation. - *

Example

- *
{@code @Inject(method = "", target = @At("io.github.opencubicchunks.cubicchunks.ConstructorSuper"))}
- */ -@AtCode("CC:SUPER") -public class ConstructorSuper extends InjectionPoint { - protected final String superName; - protected final String ownerName; - - protected final List methodNodes; - - public ConstructorSuper(InjectionPointData data) { - super(data); - if (!data.getMixin().getMixin().getConfig().getEnvironment().getVersion().equals("0.8.5")) - throw new NotImplementedException("We have updated mixin, please use CTOR_HEAD instead of this disaster."); - - ISelectorContext parent = data.getContext().getParent(); - ClassNode classNode = ((MixinTargetContext) parent.getMixin()).getTargetClassNode(); - this.superName = classNode.superName; - this.ownerName = classNode.name; - - this.methodNodes = new ArrayList<>(); - - try { - Field targetsField = parent.getClass().getSuperclass().getDeclaredField("targets"); - targetsField.setAccessible(true); - List targets = (List) targetsField.get(parent); - - if (targets.isEmpty()) { - return; - } - - Field methodField = targets.get(0).getClass().getDeclaredField("method"); - methodField.setAccessible(true); - - for (Object target : targets) { - MethodNode method = (MethodNode) methodField.get(target); - if (!method.name.equals("")) { - throw new IllegalArgumentException("ConstructorSuper inject must target a constructor"); - } - this.methodNodes.add(method); - } - } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { - throw new RuntimeException(String.format("Failed to create super injection point for method %s %s", data.getMethod().name, data.getMixin().getClassName()), e); - } - } - - @Override - public boolean checkPriority(int targetPriority, int ownerPriority) { - return true; - } - - @Override - public boolean find(String desc, InsnList insns, Collection nodes) { - for (MethodNode node : this.methodNodes) { - if (instructionsMatch(node.instructions, insns)) { - MethodInsnNode delegateInit = findDelegateInit(insns, this.superName, this.ownerName); - if (delegateInit == null) { - continue; - } - nodes.add(delegateInit.getNext()); // Assume there is always an instruction node after the super call - return true; - } - } - return false; - } - - @Nullable - public static MethodInsnNode findDelegateInit(InsnList insns, String superName, String ownerName) { - // Looking for the invocation of the super class constructor without a new before it - int news = 0; - for (AbstractInsnNode insn : insns) { - if (insn instanceof TypeInsnNode && insn.getOpcode() == Opcodes.NEW) { - news++; - } else if (insn instanceof MethodInsnNode methodInsn && insn.getOpcode() == Opcodes.INVOKESPECIAL) { - if (Constants.CTOR.equals(methodInsn.name)) { - if (news > 0) { - news--; - } else { - boolean isSuper = methodInsn.owner.equals(superName); - if (isSuper || methodInsn.owner.equals(ownerName)) { - return methodInsn; - } - } - } - } - } - return null; - } - - private boolean instructionsMatch(InsnList a, InsnList b) { - if (a.size() != b.size()) { - return false; - } - - for (int i = 0; i < a.size(); i++) { - if (a.get(i) != b.get(i)) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/multiplayer/ClientCubeCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/multiplayer/ClientCubeCache.java index 227adbdc..72a8d7d7 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/multiplayer/ClientCubeCache.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/client/multiplayer/ClientCubeCache.java @@ -2,6 +2,7 @@ import static io.github.notstirred.dasm.api.annotations.transform.Visibility.*; +import java.util.Map; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Consumer; @@ -13,14 +14,22 @@ import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; import io.github.notstirred.dasm.api.annotations.transform.Visibility; import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeSource; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.SectionPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.Heightmap; public interface ClientCubeCache extends CubeSource { // TODO (P2) we might want a version of the vanilla replaceWithPacketData with a different signature for handling chunks, since we only need heightmap data with CC @@ -34,7 +43,7 @@ public interface ClientCubeCache extends CubeSource { int y, int z, FriendlyByteBuf buffer, - CompoundTag tag, + Map map, Consumer consumer ); @@ -47,6 +56,7 @@ public interface ClientCubeCache extends CubeSource { @Dasm(ChunkToCubeSet.class) final class Storage { public final AtomicReferenceArray chunks; + final LongOpenHashSet loadedEmptySections = new LongOpenHashSet(); public final int cubeRadius; private final int viewRange; public volatile int viewCenterX; @@ -71,6 +81,7 @@ public void replace(int chunkIndex, @Nullable LevelCube chunk) { LevelCube levelchunk = this.chunks.getAndSet(chunkIndex, chunk); if (levelchunk != null) { --this.chunkCount; + this.dropEmptySections(levelchunk); // this.level.unload(levelchunk); // TODO P2 } @@ -79,13 +90,73 @@ public void replace(int chunkIndex, @Nullable LevelCube chunk) { } } - public LevelCube replace(int chunkIndex, LevelCube chunk, @Nullable LevelCube replaceWith) { - if (this.chunks.compareAndSet(chunkIndex, chunk, replaceWith) && replaceWith == null) { - --this.chunkCount; + public void drop(int chunkIndex, LevelCube chunk) { + if (this.chunks.compareAndSet(chunkIndex, chunk, null)) { + this.chunkCount--; + this.dropEmptySections(chunk); } // this.level.unload(chunk); // TODO P2 - return chunk; + } + + public void onSectionEmptinessChanged(int x, int y, int z, boolean isEmpty) { + if (this.inRange(x, y, z)) { + long i = SectionPos.asLong(x, y, z); + if (isEmpty) { + this.loadedEmptySections.add(i); + } else if (this.loadedEmptySections.remove(i)) { +// ClientChunkCache.this.level.onSectionBecomingNonEmpty(i); // TODO P2 + } + } + } + + public void dropEmptySections(LevelCube chunk) { + var cubePos = chunk.cc_getCubePos(); + + for (int dx = 0; dx < CubicConstants.DIAMETER_IN_SECTIONS; dx++) { + for (int dy = 0; dy < CubicConstants.DIAMETER_IN_SECTIONS; dy++) { + for (int dz = 0; dz < CubicConstants.DIAMETER_IN_SECTIONS; dz++) { + long sectionPosLong = SectionPos.asLong(Coords.cubeToSection(cubePos.getX(), dx), Coords.cubeToSection(cubePos.getY(), dy), Coords.cubeToSection(cubePos.getZ(), dz)); + this.loadedEmptySections.remove(sectionPosLong); + } + } + } + } + + public void addEmptySections(LevelCube chunk) { + var cubePos = chunk.cc_getCubePos(); + LevelChunkSection[] chunkSections = chunk.getSections(); + + for (int dx = 0; dx < CubicConstants.DIAMETER_IN_SECTIONS; dx++) { + for (int dy = 0; dy < CubicConstants.DIAMETER_IN_SECTIONS; dy++) { + for (int dz = 0; dz < CubicConstants.DIAMETER_IN_SECTIONS; dz++) { + var chunkSection = chunkSections[Coords.sectionToIndex(dx, dy, dz)]; + long sectionPosLong = SectionPos.asLong(Coords.cubeToSection(cubePos.getX(), dx), Coords.cubeToSection(cubePos.getY(), dy), Coords.cubeToSection(cubePos.getZ(), dz)); + if (chunkSection.hasOnlyAir()) { + this.loadedEmptySections.add(sectionPosLong); + } + } + } + } + } + + public void refreshEmptySections(LevelCube chunk) { + var cubePos = chunk.cc_getCubePos(); + LevelChunkSection[] chunkSections = chunk.getSections(); + + for (int dx = 0; dx < CubicConstants.DIAMETER_IN_SECTIONS; dx++) { + for (int dy = 0; dy < CubicConstants.DIAMETER_IN_SECTIONS; dy++) { + for (int dz = 0; dz < CubicConstants.DIAMETER_IN_SECTIONS; dz++) { + var chunkSection = chunkSections[Coords.sectionToIndex(dx, dy, dz)]; + long sectionPosLong = SectionPos.asLong(Coords.cubeToSection(cubePos.getX(), dx), Coords.cubeToSection(cubePos.getY(), dy), Coords.cubeToSection(cubePos.getZ(), dz)); + if (chunkSection.hasOnlyAir()) { + this.loadedEmptySections.add(sectionPosLong); + } else if (this.loadedEmptySections.remove(sectionPosLong)) { +// ClientChunkCache.this.level.onSectionBecomingNonEmpty(sectionPosLong); // TODO P2 + } + } + } + } } public boolean inRange(int x, int y, int z) { diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicLevelRenderer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicLevelRenderer.java index 6ee4e335..64459643 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicLevelRenderer.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicLevelRenderer.java @@ -3,5 +3,5 @@ import io.github.opencubicchunks.cc_core.api.CubePos; public interface CubicLevelRenderer { - void cc_onCubeLoaded(CubePos cubePos); + void cc_onCubeReadyToRender(CubePos cubePos); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicViewArea.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicViewArea.java deleted file mode 100644 index 5cfd97ab..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/CubicViewArea.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.client.renderer; - -public interface CubicViewArea { - void cc_repositionCamera(double viewEntityX, double viewEntityY, double viewEntityZ); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/CubicRenderRegionCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/CubicRenderRegionCache.java index a139ffc0..fdb504a8 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/CubicRenderRegionCache.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/CubicRenderRegionCache.java @@ -2,9 +2,9 @@ import javax.annotation.Nullable; -import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; import net.minecraft.world.level.Level; public interface CubicRenderRegionCache { - @Nullable RenderCubeRegion cc_createRegion(Level level, BlockPos start, BlockPos end, int padding, boolean nullForEmpty); + @Nullable RenderCubeRegion cc_createRegion(Level level, SectionPos sectionPos, boolean nullForEmpty); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCube.java index 6599963c..16d8babd 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCube.java @@ -2,19 +2,25 @@ import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromClass; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import net.minecraft.client.renderer.chunk.RenderChunk; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +/** + * The vanilla {@link RenderChunk} wraps a {@link LevelChunk} and is used to get data for rendering that chunk. + * Similarly, {@code RenderCube} wraps a {@link LevelCube} and is used to get data for rendering that cube. + */ // Whole class redirect @TransformFromClass(value = @Ref(RenderChunk.class), sets = ChunkToCubeSet.class) public class RenderCube { // Methods copied by DASM public RenderCube(LevelCube wrapped) { - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } public native BlockEntity getBlockEntity(BlockPos pos); // This method is modified with mixin diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCubeRegion.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCubeRegion.java index 9b2784da..14f654bb 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCubeRegion.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderCubeRegion.java @@ -3,50 +3,55 @@ import javax.annotation.Nullable; import io.github.opencubicchunks.cc_core.utils.Coords; +import net.minecraft.client.renderer.chunk.RenderChunk; import net.minecraft.client.renderer.chunk.RenderChunkRegion; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.material.FluidState; +/** + * The vanilla {@link RenderChunkRegion} stores a 3x3 of {@link RenderChunk}s and is used to get data for rendering a single {@link LevelChunkSection} in the center of the 3x3. + * Similarly, {@code RenderCubeRegion} stores a 3x3x3 of {@link RenderCube}s and is used to get data for rendering a single {@link LevelChunkSection} in the center of the 3x3x3. + */ public class RenderCubeRegion extends RenderChunkRegion { - private final int centerX; - private final int centerY; - private final int centerZ; - protected final RenderCube[][][] cubes; + private final int minCubeX; + private final int minCubeY; + private final int minCubeZ; + protected final RenderCube[] cubes; - public RenderCubeRegion(Level level, int centerX, int centerY, int centerZ, RenderCube[][][] cubes, @Nullable net.neoforged.neoforge.client.model.data.ModelDataManager.Snapshot modelDataManager) { + public RenderCubeRegion(Level level, int minCubeX, int minCubeY, int minCubeZ, RenderCube[] cubes, @Nullable it.unimi.dsi.fastutil.longs.Long2ObjectFunction modelDataSnapshot) { super(level, 0, 0, null); // TODO set modelDataManager on parent - requires an accessor mixin since we can't AT the NF constructor or field - this.centerX = centerX; - this.centerY = centerY; - this.centerZ = centerZ; + this.minCubeX = minCubeX; + this.minCubeY = minCubeY; + this.minCubeZ = minCubeZ; this.cubes = cubes; } @Override public BlockState getBlockState(BlockPos pos) { - int x = Coords.blockToCube(pos.getX()) - this.centerX; - int y = Coords.blockToCube(pos.getY()) - this.centerY; - int z = Coords.blockToCube(pos.getZ()) - this.centerZ; - return this.cubes[x][y][z].getBlockState(pos); + return this.getCube(Coords.blockToCube(pos.getX()), Coords.blockToCube(pos.getY()), Coords.blockToCube(pos.getZ())).getBlockState(pos); } @Override public FluidState getFluidState(BlockPos pos) { - int x = Coords.blockToCube(pos.getX()) - this.centerX; - int y = Coords.blockToCube(pos.getY()) - this.centerY; - int z = Coords.blockToCube(pos.getZ()) - this.centerZ; - return this.cubes[x][y][z].getBlockState(pos).getFluidState(); + return this.getCube(Coords.blockToCube(pos.getX()), Coords.blockToCube(pos.getY()), Coords.blockToCube(pos.getZ())).getBlockState(pos).getFluidState(); } @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { - int x = Coords.blockToCube(pos.getX()) - this.centerX; - int y = Coords.blockToCube(pos.getY()) - this.centerY; - int z = Coords.blockToCube(pos.getZ()) - this.centerZ; - return this.cubes[x][y][z].getBlockEntity(pos); + return this.getCube(Coords.blockToCube(pos.getX()), Coords.blockToCube(pos.getY()), Coords.blockToCube(pos.getZ())).getBlockEntity(pos); + } + + private RenderCube getCube(int cubeX, int cubeY, int cubeZ) { + return this.cubes[index(this.minCubeX, this.minCubeY, this.minCubeZ, cubeX, cubeY, cubeZ)]; + } + + public static int index(int minCubeX, int minCubeY, int minCubeZ, int cubeX, int cubeY, int cubeZ) { + return cubeX - minCubeX + (cubeZ - minCubeZ) * 3 + (cubeY - minCubeY) * 9; } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderRegionCacheCubeInfo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderRegionCacheCubeInfo.java index c865063d..71a91e1f 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderRegionCacheCubeInfo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/client/renderer/cube/RenderRegionCacheCubeInfo.java @@ -4,16 +4,16 @@ import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromClass; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; -import net.minecraft.client.renderer.chunk.RenderRegionCache; // Cubic equivalent to RenderRegionCache$ChunkInfo, since we can't add inner classes with mixin // Whole class redirect @TransformFromClass(value = @Ref(string = "net.minecraft.client.renderer.chunk.RenderRegionCache$ChunkInfo"), sets = ChunkToCubeSet.class) public class RenderRegionCacheCubeInfo { public RenderRegionCacheCubeInfo(LevelCube cube) { - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } @AddMethodToSets(owner = @Ref(string = "net.minecraft.client.renderer.chunk.RenderRegionCache$ChunkInfo"), sets = ChunkToCubeSet.class, method = @MethodSig("chunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/exception/DasmFailedToApply.java b/src/main/java/io/github/opencubicchunks/cubicchunks/exception/DasmFailedToApply.java new file mode 100644 index 00000000..95d5a3b8 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/exception/DasmFailedToApply.java @@ -0,0 +1,7 @@ +package io.github.opencubicchunks.cubicchunks.exception; + +public class DasmFailedToApply extends IllegalStateException { + public DasmFailedToApply() { + super("Dasm failed to apply"); + } +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/exception/package-info.java similarity index 75% rename from src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/package-info.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/exception/package-info.java index e57d7ac7..0df0f87e 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/package-info.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/exception/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.test.common; +package io.github.opencubicchunks.cubicchunks.exception; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/client/ViewAreaAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/client/ViewAreaAccess.java index e374557b..35aac436 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/client/ViewAreaAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/client/ViewAreaAccess.java @@ -8,5 +8,5 @@ @Mixin(ViewArea.class) public interface ViewAreaAccess { - @Invoker("getRenderSectionAt") SectionRenderDispatcher.RenderSection cc_invokeGetRenderSectionAt(BlockPos pos); + @Invoker("getRenderSection") SectionRenderDispatcher.RenderSection cc_invokeGetRenderSection(long sectionPosLong); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/DistanceManagerAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/DistanceManagerAccess.java index fd8933f7..ea603061 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/DistanceManagerAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/DistanceManagerAccess.java @@ -1,11 +1,11 @@ package io.github.opencubicchunks.cubicchunks.mixin.access.common; -import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.ThrottlingChunkTaskDispatcher; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(DistanceManager.class) public interface DistanceManagerAccess { - @Accessor("ticketThrottler") ChunkTaskPriorityQueueSorter cc_ticketThrottler(); + @Accessor("ticketDispatcher") ThrottlingChunkTaskDispatcher cc_ticketDispatcher(); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/GenerationChunkHolderAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/GenerationChunkHolderAccess.java new file mode 100644 index 00000000..0ef07a11 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/GenerationChunkHolderAccess.java @@ -0,0 +1,11 @@ +package io.github.opencubicchunks.cubicchunks.mixin.access.common; + +import net.minecraft.server.level.ChunkGenerationTask; +import net.minecraft.server.level.GenerationChunkHolder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(GenerationChunkHolder.class) +public interface GenerationChunkHolderAccess { + @Invoker("removeTask") void cc_invokeRemoveTask(ChunkGenerationTask task); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/TicketTypeAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/TicketTypeAccess.java deleted file mode 100644 index 7fc51206..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/access/common/TicketTypeAccess.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.access.common; - -import java.util.Comparator; -import net.minecraft.server.level.TicketType; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(TicketType.class) -public interface TicketTypeAccess { - @Invoker("") static TicketType cc_createNew(String string, Comparator comparator, long timeout) { - throw new Error("Mixin did not apply"); - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientChunkCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientChunkCache.java similarity index 94% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientChunkCache.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientChunkCache.java index 1dce06b9..10668521 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientChunkCache.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientChunkCache.java @@ -1,8 +1,9 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.multiplayer; import static io.github.opencubicchunks.cc_core.CubicChunksBase.LOGGER; import static io.github.opencubicchunks.cc_core.utils.Coords.cubeToSection; +import java.util.Map; import java.util.function.Consumer; import javax.annotation.Nullable; @@ -27,7 +28,8 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.world.level.biome.Biomes; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.Heightmap; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -57,7 +59,7 @@ public abstract class MixinClientChunkCache extends MixinChunkSource implements private void cc_onConstruct(ClientLevel level, int viewDistance, CallbackInfo ci) { if (((CanBeCubic) level).cc_isCubic()) { cc_emptyCube = new EmptyLevelCube( - level, CubePos.of(0, 0, 0), level.registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS) + level, CubePos.of(0, 0, 0), level.registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS) ); cc_cubeStorage = new ClientCubeCache.Storage(calculateStorageRange(viewDistance), level); // TODO we could redirect the initial construction instead of immediately resizing. doesn't really matter @@ -82,7 +84,7 @@ public void cc_drop(CubePos chunkPos) { if (cc_isValidCube(levelCube, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ())) { // TODO event hook // net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.level.ChunkEvent.Unload(levelCube)); - this.cc_cubeStorage.replace(i, levelCube, null); + this.cc_cubeStorage.drop(i, levelCube); } } } @@ -121,7 +123,7 @@ public void cc_replaceBiomes(int x, int y, int z, FriendlyByteBuf buffer) { int y, int z, FriendlyByteBuf buffer, - CompoundTag tag, + Map map, Consumer consumer ) { if (!this.cc_cubeStorage.inRange(x, y, z)) { @@ -133,10 +135,11 @@ public void cc_replaceBiomes(int x, int y, int z, FriendlyByteBuf buffer) { CubePos cubePos = CubePos.of(x, y, z); if (!cc_isValidCube(levelCube, x, y, z)) { levelCube = new LevelCube(this.level, cubePos); - levelCube.replaceWithPacketData(buffer, tag, consumer); + levelCube.replaceWithPacketData(buffer, map, consumer); this.cc_cubeStorage.replace(i, levelCube); } else { - levelCube.replaceWithPacketData(buffer, tag, consumer); + levelCube.replaceWithPacketData(buffer, map, consumer); + this.cc_cubeStorage.refreshEmptySections(levelCube); } ((CubicClientLevel) this.level).cc_onCubeLoaded(cubePos); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientCubeCache$Storage.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientCubeCache$Storage.java similarity index 73% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientCubeCache$Storage.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientCubeCache$Storage.java index 0c1feafa..48efacca 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientCubeCache$Storage.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientCubeCache$Storage.java @@ -1,4 +1,4 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.multiplayer; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientLevel.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientLevel.java similarity index 83% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientLevel.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientLevel.java index 84c3ce5b..fd34a481 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/MixinClientLevel.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/MixinClientLevel.java @@ -1,9 +1,8 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.multiplayer; import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; import io.github.opencubicchunks.cubicchunks.client.multiplayer.CubicClientLevel; -import io.github.opencubicchunks.cubicchunks.client.renderer.CubicLevelRenderer; import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.MixinLevel; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; @@ -15,7 +14,6 @@ @Mixin(ClientLevel.class) public abstract class MixinClientLevel extends MixinLevel implements CubicClientLevel { @Shadow @Final private ClientChunkCache chunkSource; - @Shadow @Final private LevelRenderer levelRenderer; @Override public boolean cc_hasCube(int cubeX, int cubeY, int cubeZ) { return true; @@ -25,13 +23,14 @@ public abstract class MixinClientLevel extends MixinLevel implements CubicClient public void cc_onCubeLoaded(CubePos cubePos) { // this.tintCaches.forEach((p_194154_, p_194155_) -> p_194155_.invalidateForChunk(chunkPos.x, chunkPos.z)); // this.entityStorage.startTicking(chunkPos); - ((CubicLevelRenderer) this.levelRenderer).cc_onCubeLoaded(cubePos); } @Override public ClientCubeCache cc_getCubeSource() { return ((ClientCubeCache) this.chunkSource); } + // TODO: comments below don't account for 1.20.4->1.21.5 changes; will need to check for other methods that need CC changes + // unload // TODO: Phase 2 - this interacts with the lighting engine and will need to change to support cubes diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/package-info.java similarity index 71% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/package-info.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/package-info.java index 33a088cf..1fe48ec0 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/package-info.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/multiplayer/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.multiplayer; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinLevelRenderer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinLevelRenderer.java new file mode 100644 index 00000000..36cd3b65 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinLevelRenderer.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.client.renderer.CubicLevelRenderer; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import net.minecraft.client.renderer.LevelRenderer; +import org.spongepowered.asm.mixin.Mixin; + +@Dasm(ChunkToCubeSet.class) +@Mixin(LevelRenderer.class) +public abstract class MixinLevelRenderer implements CubicLevelRenderer { + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(@MethodSig("onChunkReadyToRender(Lnet/minecraft/world/level/ChunkPos;)V")) + public native void cc_onCubeReadyToRender(CubePos cubePos); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinSectionOcclusionGraph$GraphStorage.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinSectionOcclusionGraph$GraphStorage.java new file mode 100644 index 00000000..2fcd8431 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinSectionOcclusionGraph$GraphStorage.java @@ -0,0 +1,24 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.opencubicchunks.cubicchunks.CanBeCubic; +import net.minecraft.client.renderer.Octree; +import net.minecraft.client.renderer.ViewArea; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(targets = "net.minecraft.client.renderer.SectionOcclusionGraph$GraphStorage") +public class MixinSectionOcclusionGraph$GraphStorage { + /** + * Wrap reading of sectionGridSizeY when constructing {@link Octree} to ensure that the Octree does not have special-cased Y axis behavior (as occurs in vanilla worlds due to the height limit) + */ + @WrapOperation(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/ViewArea;sectionGridSizeY:I")) + private int cc_onInit_constructOctree_getMinY(ViewArea instance, Operation original) { + if (((CanBeCubic) instance.getLevelHeightAccessor()).cc_isCubic()) { + // Passing MAX_VALUE for sectionGridSizeY when constructing the Octree ensures that the Y axis is not special-cased and behaves the same as the X and Z axes. + return Integer.MAX_VALUE; + } + return original.call(instance); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinSectionOcclusionGraph.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinSectionOcclusionGraph.java similarity index 58% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinSectionOcclusionGraph.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinSectionOcclusionGraph.java index f8be7583..6268f312 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinSectionOcclusionGraph.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinSectionOcclusionGraph.java @@ -1,4 +1,4 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer; import javax.annotation.Nullable; @@ -22,6 +22,7 @@ import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.SectionPos; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -35,63 +36,68 @@ public abstract class MixinSectionOcclusionGraph { private boolean cc_isCubic = false; @Shadow @Nullable private ViewArea viewArea; - @Shadow protected abstract boolean isInViewDistance(BlockPos pos, BlockPos origin); + @Shadow protected abstract boolean isInViewDistance(long centerSectionPos, long sectionPos); @Inject(method = "waitAndReset", at = @At("RETURN")) private void cc_onWaitAndReset(@Nullable ViewArea viewArea, CallbackInfo ci) { cc_isCubic = viewArea != null && ((CanBeCubic) viewArea.getLevelHeightAccessor()).cc_isCubic(); } - @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(@MethodSig("onChunkLoaded(Lnet/minecraft/world/level/ChunkPos;)V")) - public native void cc_onCubeLoaded(CubePos cubePos); + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(@MethodSig("onChunkReadyToRender(Lnet/minecraft/world/level/ChunkPos;)V")) + public native void cc_onCubeReadyToRender(CubePos cubePos); @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(SectionOcclusionGraph.class), method = @MethodSig("addNeighbors(Lnet/minecraft/client/renderer/SectionOcclusionGraph$GraphEvents;Lnet/minecraft/world/level/ChunkPos;)V")) private void cc_addNeighbors(SectionOcclusionGraph.GraphEvents graphEvents, CubePos cubePos) { var access = ((SectionOcclusionGraph$GraphEventsAccess) (Object) graphEvents); - access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubePos.getX() - 1, cubePos.getY(), cubePos.getZ())); - access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubePos.getX(), cubePos.getY() - 1, cubePos.getZ())); - access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubePos.getX(), cubePos.getY(), cubePos.getZ() - 1)); - access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubePos.getX() + 1, cubePos.getY(), cubePos.getZ())); - access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubePos.getX(), cubePos.getY() + 1, cubePos.getZ())); - access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubePos.getX(), cubePos.getY(), cubePos.getZ() + 1)); + int cubeX = cubePos.getX(); + int cubeY = cubePos.getY(); + int cubeZ = cubePos.getZ(); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0 && dz == 0) continue; + access.cc_chunksWhichReceivedNeighbors().add(CubePos.asLong(cubeX+dx, cubeY+dy, cubeZ+dz)); + } + } + } } - @WrapOperation(method = "initializeQueueForFullUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ViewArea;getRenderSectionAt(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;")) - private @Nullable SectionRenderDispatcher.RenderSection cc_onInitializeQueueForFullUpdate_getRenderSectionAt(ViewArea instance, BlockPos pos, Operation original) { - var result = original.call(instance, pos); + @WrapOperation(method = "initializeQueueForFullUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ViewArea;getRenderSection(J)Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;")) + private @Nullable SectionRenderDispatcher.RenderSection cc_onInitializeQueueForFullUpdate_getRenderSectionAt(ViewArea instance, long sectionPos, Operation original) { + var result = original.call(instance, sectionPos); if (result == null && cc_isCubic) { throw new IllegalStateException("getRenderSectionAt should never return null in a cubic world"); } return result; } - @WrapOperation(method = "runUpdates", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/ChunkPos;asLong(Lnet/minecraft/core/BlockPos;)J")) - private long cc_onRunUpdates_chunkPosAsLong(BlockPos pos, Operation original) { - if (!cc_isCubic) return original.call(pos); - return CubePos.asLong(pos); + @WrapOperation(method = "runUpdates", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/SectionPos;sectionToChunk(J)J")) + private long cc_onRunUpdates_sectionToChunk(long sectionPosLong, Operation original) { + if (!cc_isCubic) return original.call(sectionPosLong); + return CubePos.asLong(SectionPos.x(sectionPosLong), SectionPos.y(sectionPosLong), SectionPos.z(sectionPosLong)); } @Inject(method = "isInViewDistance", at = @At("HEAD"), cancellable = true) - private void cc_onIsInViewDistance(BlockPos pos, BlockPos origin, CallbackInfoReturnable cir) { + private void cc_onIsInViewDistance(long originSectionPosLong, long sectionPosLong, CallbackInfoReturnable cir) { if (!cc_isCubic) return; - int posCubeX = Coords.blockToCube(pos.getX()); - int posCubeY = Coords.blockToCube(pos.getY()); - int posCubeZ = Coords.blockToCube(pos.getZ()); - int originCubeX = Coords.blockToCube(origin.getX()); - int originCubeY = Coords.blockToCube(origin.getY()); - int originCubeZ = Coords.blockToCube(origin.getZ()); - cir.setReturnValue(CloTrackingView.cc_isInViewDistance(posCubeX, posCubeY, posCubeZ, Coords.sectionToCubeRenderDistance(this.viewArea.getViewDistance()), originCubeX, originCubeY, originCubeZ)); + int originCubeX = Coords.blockToCube(SectionPos.x(originSectionPosLong)); + int originCubeY = Coords.blockToCube(SectionPos.y(originSectionPosLong)); + int originCubeZ = Coords.blockToCube(SectionPos.z(originSectionPosLong)); + int posCubeX = Coords.blockToCube(SectionPos.x(sectionPosLong)); + int posCubeY = Coords.blockToCube(SectionPos.y(sectionPosLong)); + int posCubeZ = Coords.blockToCube(SectionPos.z(sectionPosLong)); + cir.setReturnValue(CloTrackingView.cc_isInViewDistance(originCubeX, originCubeY, originCubeZ, Coords.sectionToCubeRenderDistance(this.viewArea.getViewDistance()), posCubeX, posCubeY, posCubeZ)); } @Inject(method = "getRelativeFrom", at = @At("HEAD"), cancellable = true) - private void cc_onGetRelativeFrom(BlockPos pos, SectionRenderDispatcher.RenderSection section, Direction direction, CallbackInfoReturnable cir) { + private void cc_onGetRelativeFrom(long sectionPosLong, SectionRenderDispatcher.RenderSection section, Direction direction, CallbackInfoReturnable cir) { if (!cc_isCubic) return; // Same as vanilla logic but we don't manually check Y coordinates since that's handled by isInViewDistance now - BlockPos relativeOrigin = section.getRelativeOrigin(direction); - if (!this.isInViewDistance(pos, relativeOrigin)) { + long sectionPosLong2 = section.getNeighborSectionNode(direction); + if (!this.isInViewDistance(sectionPosLong, sectionPosLong2)) { cir.setReturnValue(null); } else { - cir.setReturnValue(((ViewAreaAccess) this.viewArea).cc_invokeGetRenderSectionAt(relativeOrigin)); + cir.setReturnValue(((ViewAreaAccess) this.viewArea).cc_invokeGetRenderSection(sectionPosLong2)); } } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinViewArea.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinViewArea.java new file mode 100644 index 00000000..eaca58f6 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/MixinViewArea.java @@ -0,0 +1,100 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cubicchunks.CanBeCubic; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.ViewArea; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ViewArea.class) +public abstract class MixinViewArea { + @Shadow @Final protected Level level; + @Shadow protected int sectionGridSizeY; + @Shadow protected int sectionGridSizeX; + @Shadow protected int sectionGridSizeZ; + @Shadow private int viewDistance; + @Shadow public SectionRenderDispatcher.RenderSection[] sections; + @Shadow private SectionPos cameraSectionPos; + @Shadow @Final protected LevelRenderer levelRenderer; + + @Shadow protected abstract int getSectionIndex(int x, int y, int z); + + // This could be multiple more specific injects, but overwriting the method is probably cleaner + @Inject(method = "setViewDistance", at = @At("HEAD"), cancellable = true) + private void cc_onSetViewDistance(int renderDistanceChunks, CallbackInfo ci) { + if (!((CanBeCubic) level).cc_isCubic()) return; + ci.cancel(); + int i = renderDistanceChunks * 2 + 1; + this.sectionGridSizeX = i; + this.sectionGridSizeY = i; + this.sectionGridSizeZ = i; + this.viewDistance = renderDistanceChunks; + } + + @WrapOperation(method = "createSections", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getMinSectionY()I")) + private int cc_onCreateSections_getMinY(Level instance, Operation original) { + if (!((CanBeCubic) level).cc_isCubic()) return original.call(instance); + return 0; // I don't really understand the logic here, but returning 0 makes the Y axis behave equivalently to X and Z, which *should* be what we want + } + + // TODO can we do this without fully overwriting the method? + @Inject(method = "getRenderSection(III)Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;", at = @At("HEAD"), cancellable = true) + private void cc_onGetRenderSection(int sectionX, int sectionY, int sectionZ, CallbackInfoReturnable cir) { + if (!((CanBeCubic) level).cc_isCubic()) return; + if (!this.cc_containsSection(sectionX, sectionY, sectionZ)) { + cir.setReturnValue(null); + return; + } + int x = Math.floorMod(sectionX, this.sectionGridSizeX); + int y = Math.floorMod(sectionY, this.sectionGridSizeY); + int z = Math.floorMod(sectionZ, this.sectionGridSizeZ); + cir.setReturnValue(this.sections[this.getSectionIndex(x, y, z)]); + } + + // TODO can we do this with dasm + mixin? probably too messy + // I don't really understand the coordinate maths here, but we replicate it for the Y axis + @Inject(method = "repositionCamera", at = @At("HEAD"), cancellable = true) + private void cc_onRepositionCamera(SectionPos newSectionPos, CallbackInfo ci) { + if (!((CanBeCubic) level).cc_isCubic()) return; + ci.cancel(); + for (int sectionX = 0; sectionX < this.sectionGridSizeX; sectionX++) { + int i1 = newSectionPos.x() - this.viewDistance; + int originX = i1 + Math.floorMod(sectionX - i1, this.sectionGridSizeX); + + for (int sectionZ = 0; sectionZ < this.sectionGridSizeZ; sectionZ++) { + int i2 = newSectionPos.z() - this.viewDistance; + int originZ = i2 + Math.floorMod(sectionZ - i2, this.sectionGridSizeZ); + + for (int sectionY = 0; sectionY < this.sectionGridSizeY; sectionY++) { + int i3 = newSectionPos.y() - this.viewDistance; + int originY = i3 + Math.floorMod(sectionY - i3, this.sectionGridSizeY); + SectionRenderDispatcher.RenderSection renderSection = this.sections[this.getSectionIndex(sectionX, sectionY, sectionZ)]; + long oldSectionNode = renderSection.getSectionNode(); + if (oldSectionNode != SectionPos.asLong(originX, originY, originZ)) { + renderSection.setSectionNode(SectionPos.asLong(originX, originY, originZ)); + } + } + } + } + + this.cameraSectionPos = newSectionPos; + this.levelRenderer.getSectionOcclusionGraph().invalidate(); + } + + private boolean cc_containsSection(int x, int y, int z) { + return x >= this.cameraSectionPos.x() - this.viewDistance && x <= this.cameraSectionPos.x() + this.viewDistance + && y >= this.cameraSectionPos.y() - this.viewDistance && y <= this.cameraSectionPos.y() + this.viewDistance + && z >= this.cameraSectionPos.z() - this.viewDistance && z <= this.cameraSectionPos.z() + this.viewDistance; + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/MixinRenderRegionCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/MixinRenderRegionCache.java new file mode 100644 index 00000000..74cbb4d3 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/MixinRenderRegionCache.java @@ -0,0 +1,75 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer.chunk; + +import javax.annotation.Nullable; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.client.renderer.cube.CubicRenderRegionCache; +import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCube; +import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCubeRegion; +import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderRegionCacheCubeInfo; +import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.client.renderer.chunk.RenderChunk; +import net.minecraft.client.renderer.chunk.RenderChunkRegion; +import net.minecraft.client.renderer.chunk.RenderRegionCache; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +/** + * The vanilla {@link RenderRegionCache} caches {@link LevelChunk}s and {@link RenderChunk}s while rebuilding {@link SectionRenderDispatcher.RenderSection}s + * and has a method for creating a {@link RenderChunkRegion} for a given {@link SectionPos}. + *

+ * We modify it by additionally caching {@link LevelCube}s and {@link RenderCube}s, and adding an equivalent method to create a {@link RenderCubeRegion} for a given {@link SectionPos}. + */ +@Mixin(RenderRegionCache.class) +public abstract class MixinRenderRegionCache implements CubicRenderRegionCache { + @Shadow private final Long2ObjectMap chunkInfoCache = new Long2ObjectOpenHashMap<>(); + + // TODO can we possibly do this with DASM + mixin? probably not? + @Override @Nullable public RenderCubeRegion cc_createRegion(Level level, SectionPos sectionPos, boolean nullForEmpty) { + int centerCubeX = Coords.sectionToCube(sectionPos.getX()); + int centerCubeY = Coords.sectionToCube(sectionPos.getY()); + int centerCubeZ = Coords.sectionToCube(sectionPos.getZ()); + var centerCubeInfo = this.cc_getChunkInfo(level, centerCubeX, centerCubeY, centerCubeZ); +// if (nullForEmpty && centerCubeInfo.cube().isSectionEmpty(sectionPos.y())) { // TODO need a proper isSectionEmpty on CubeAccess +// return null; +// } + int cubeStartX = centerCubeX - 1; + int cubeStartY = centerCubeY - 1; + int cubeStartZ = centerCubeZ - 1; + int cubeEndX = centerCubeX + 1; + int cubeEndY = centerCubeY + 1; + int cubeEndZ = centerCubeZ + 1; + RenderCube[] renderCubes = new RenderCube[27]; + + for(int cubeX = cubeStartX; cubeX <= cubeEndX; ++cubeX) { + for(int cubeY = cubeStartY; cubeY <= cubeEndY; ++cubeY) { + for(int cubeZ = cubeStartZ; cubeZ <= cubeEndZ; ++cubeZ) { + int cubeIndex = RenderCubeRegion.index(cubeStartX, cubeStartY, cubeStartZ, cubeX, cubeY, cubeZ); + var cubeInfo = cubeX == centerCubeX && cubeY == centerCubeY && cubeZ == centerCubeZ + ? centerCubeInfo + : this.cc_getChunkInfo(level, cubeX, cubeY, cubeZ); + renderCubes[cubeIndex] = cubeInfo.renderCube(); + } + } + } + + var modelDataManager = level.getModelDataManager().snapshotSectionRegion(cubeStartX, cubeStartY, cubeStartZ, cubeEndX, cubeEndY, cubeEndZ); + return new RenderCubeRegion(level, cubeStartX, cubeStartY, cubeStartZ, renderCubes, modelDataManager); + } + + private RenderRegionCacheCubeInfo cc_getChunkInfo(Level level, int cubeX, int cubeY, int cubeZ) { + return this.chunkInfoCache + .computeIfAbsent( + CubePos.asLong(cubeX, cubeY, cubeZ), + cubePosLong -> new RenderRegionCacheCubeInfo(((CubicLevel) level).cc_getCube(CubePos.extractX(cubePosLong), CubePos.extractY(cubePosLong), CubePos.extractZ(cubePosLong))) + ); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/MixinSectionRenderDispatcher$RenderSection.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/MixinSectionRenderDispatcher$RenderSection.java new file mode 100644 index 00000000..dd5b9588 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/MixinSectionRenderDispatcher$RenderSection.java @@ -0,0 +1,79 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer.chunk; + +import javax.annotation.Nullable; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.CanBeCubic; +import io.github.opencubicchunks.cubicchunks.client.renderer.cube.CubicRenderRegionCache; +import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCubeRegion; +import io.github.opencubicchunks.cubicchunks.mixin.access.client.SectionRenderDispatcherAccess; +import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; +import net.minecraft.client.renderer.chunk.RenderChunkRegion; +import net.minecraft.client.renderer.chunk.RenderRegionCache; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * {@code RenderSection} represents a single {@link LevelChunkSection} to be rendered. + * We modify it to check cubes instead of chunks when validating the presence of neighboring sections in cubic levels, and to use {@link RenderCubeRegion} instead of {@link RenderChunkRegion}. + */ +@Mixin(SectionRenderDispatcher.RenderSection.class) +public abstract class MixinSectionRenderDispatcher$RenderSection { + @Shadow @Final SectionRenderDispatcher this$0; + @Shadow private volatile long sectionNode; + + @Shadow protected abstract boolean doesChunkExistAt(long sectionPosLong); + + @Inject(method = "doesChunkExistAt", at = @At("HEAD"), cancellable = true) + private void cc_onDoesChunkExistAt(long sectionPosLong, CallbackInfoReturnable cir) { + if (!((CanBeCubic) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_isCubic()) { + return; + } + // TODO (P2) lighting: also check the cubic equivalent of LevelLightEngine.lightOnInColumn here (see vanilla doesChunkExistAt method) - sections currently sometimes fail to render due to this missing check + cir.setReturnValue(((CubicLevel) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_getCube(Coords.sectionToCube(SectionPos.x(sectionPosLong)), Coords.sectionToCube(SectionPos.y(sectionPosLong)), Coords.sectionToCube(SectionPos.z(sectionPosLong)), ChunkStatus.FULL, false) != null); + } + + /** + * Check vertically offset neighbors as well as purely horizontal neighbors in cubic worlds + */ + @Inject(method = "hasAllNeighbors", cancellable = true, at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;doesChunkExistAt(J)Z")) + private void cc_onHasAllNeighbors(CallbackInfoReturnable cir) { + if (!((CanBeCubic) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_isCubic()) + return; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dy == 0 && dz == 0) continue; + if (!this.doesChunkExistAt(SectionPos.offset(this.sectionNode, dx, dy, dz))) { + cir.setReturnValue(false); + return; + } + } + } + } + cir.setReturnValue(true); + } + + + /** + * Wrap creation of RenderChunkRegion to create a RenderCubeRegion in cubic worlds (return type stays the same because RenderCubeRegion extends RenderChunkRegion) + */ + @WrapOperation(method = "createCompileTask", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/RenderRegionCache;createRegion(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/SectionPos;Z)Lnet/minecraft/client/renderer/chunk/RenderChunkRegion;")) + @Nullable private RenderChunkRegion cc_onCreateCompileTask_createRegion(RenderRegionCache instance, Level level, SectionPos sectionPos, boolean bool, + Operation original) { + if (!((CanBeCubic) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_isCubic()) + return original.call(instance, level, sectionPos, bool); + return ((CubicRenderRegionCache) instance).cc_createRegion(level, sectionPos, bool); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/package-info.java new file mode 100644 index 00000000..1845b37d --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/chunk/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer.chunk; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/MixinRenderCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/MixinRenderCube.java similarity index 91% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/MixinRenderCube.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/MixinRenderCube.java index 910fa5a1..00eb0ac9 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/MixinRenderCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/MixinRenderCube.java @@ -1,4 +1,4 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer.cube; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer.cube; import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCube; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/MixinRenderRegionCacheCubeInfo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/MixinRenderRegionCacheCubeInfo.java similarity index 75% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/MixinRenderRegionCacheCubeInfo.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/MixinRenderRegionCacheCubeInfo.java index 2c40b4aa..b29e8bb3 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/MixinRenderRegionCacheCubeInfo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/MixinRenderRegionCacheCubeInfo.java @@ -1,4 +1,4 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer.cube; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer.cube; import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderRegionCacheCubeInfo; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/package-info.java new file mode 100644 index 00000000..6805c4cb --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/cube/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer.cube; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/package-info.java new file mode 100644 index 00000000..99ca7efb --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/renderer/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.mixin.core.client.renderer; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/gui/screens/worldselection/MixinCreateWorldScreen.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/worldselection/MixinCreateWorldScreen.java similarity index 94% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/gui/screens/worldselection/MixinCreateWorldScreen.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/worldselection/MixinCreateWorldScreen.java index 71e02661..2454a068 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/gui/screens/worldselection/MixinCreateWorldScreen.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/worldselection/MixinCreateWorldScreen.java @@ -1,4 +1,4 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.gui.screens.worldselection; +package io.github.opencubicchunks.cubicchunks.mixin.core.client.worldselection; import com.llamalad7.mixinextras.sugar.Local; import io.github.opencubicchunks.cubicchunks.CubicChunks; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/worldselection/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/worldselection/package-info.java new file mode 100644 index 00000000..67d606b5 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/client/worldselection/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.mixin.core.client.worldselection; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/gui/screens/worldselection/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/gui/screens/worldselection/package-info.java deleted file mode 100644 index 5a19e4a2..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/gui/screens/worldselection/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.gui.screens.worldselection; - -import javax.annotation.ParametersAreNonnullByDefault; - -import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinLevelRenderer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinLevelRenderer.java deleted file mode 100644 index 8d4ac901..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinLevelRenderer.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer; - -import javax.annotation.Nullable; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.llamalad7.mixinextras.sugar.Local; -import io.github.notstirred.dasm.api.annotations.Dasm; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; -import io.github.notstirred.dasm.api.annotations.selector.MethodSig; -import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; -import io.github.opencubicchunks.cc_core.api.CubePos; -import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.client.renderer.CubicLevelRenderer; -import io.github.opencubicchunks.cubicchunks.client.renderer.CubicViewArea; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.renderer.LevelRenderer; -import net.minecraft.client.renderer.ViewArea; -import net.minecraft.world.entity.Entity; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; - -@Dasm(ChunkToCubeSet.class) -@Mixin(LevelRenderer.class) -public abstract class MixinLevelRenderer implements CubicLevelRenderer { - @Shadow @Nullable private ClientLevel level; - @Shadow @Final private Minecraft minecraft; - - @WrapOperation(method = "allChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ViewArea;repositionCamera(DD)V")) - private void cc_onAllChanged_repositionCamera(ViewArea viewArea, double x, double z, Operation original, @Local Entity cameraEntity) { - if (level == null || !((CanBeCubic) level).cc_isCubic()) { - original.call(viewArea, x, z); - return; - } - ((CubicViewArea) viewArea).cc_repositionCamera(cameraEntity.getX(), cameraEntity.getY(), cameraEntity.getZ()); - } - - @WrapOperation(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ViewArea;repositionCamera(DD)V")) - private void cc_onSetupRender_repositionCamera(ViewArea viewArea, double x, double z, Operation original) { - if (level == null || !((CanBeCubic) level).cc_isCubic()) { - original.call(viewArea, x, z); - return; - } - ((CubicViewArea) viewArea).cc_repositionCamera(this.minecraft.player.getX(), this.minecraft.player.getY(), this.minecraft.player.getZ()); - } - - @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(@MethodSig("onChunkLoaded(Lnet/minecraft/world/level/ChunkPos;)V")) - public native void cc_onCubeLoaded(CubePos cubePos); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinViewArea.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinViewArea.java deleted file mode 100644 index 873fa32b..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/MixinViewArea.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import io.github.opencubicchunks.cc_core.api.CubicConstants; -import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.client.renderer.CubicViewArea; -import net.minecraft.client.renderer.ViewArea; -import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; -import net.minecraft.core.BlockPos; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(ViewArea.class) -public abstract class MixinViewArea implements CubicViewArea { - @Shadow @Final protected Level level; - @Shadow protected int sectionGridSizeY; - @Shadow protected int sectionGridSizeX; - @Shadow protected int sectionGridSizeZ; - @Shadow private int viewDistance; - @Shadow public SectionRenderDispatcher.RenderSection[] sections; - - @Shadow protected abstract int getSectionIndex(int x, int y, int z); - - // FIXME inject properly instead of overwriting the method - @Inject(method = "setViewDistance", at = @At("HEAD"), cancellable = true) - private void cc_onSetViewDistance(int renderDistanceChunks, CallbackInfo ci) { - if (!((CanBeCubic) level).cc_isCubic()) return; - ci.cancel(); - int i = renderDistanceChunks * 2 + 1; - i *= CubicConstants.DIAMETER_IN_SECTIONS; // TODO is this correct? - this.sectionGridSizeX = i; - this.sectionGridSizeY = i; - this.sectionGridSizeZ = i; - this.viewDistance = renderDistanceChunks; - } - - @WrapOperation(method = "createSections", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getMinBuildHeight()I")) - private int cc_onCreateSections_getMinBuildHeight(Level instance, Operation original) { - if (!((CanBeCubic) level).cc_isCubic()) return original.call(instance); - return 0; // I don't really understand the logic here, but returning 0 makes the Y axis behave equivalently to X and Z, which *should* be what we want - } - - @WrapOperation(method = "setDirty", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getMinSection()I")) - private int cc_onSetDirty_getMinSection(Level instance, Operation original) { - if (!((CanBeCubic) level).cc_isCubic()) return original.call(instance); - return 0; // As above, returning 0 makes the Y axis behave equivalently to X and Z - } - - // TODO can we do this without fully overwriting the method? - @Inject(method = "getRenderSectionAt", at = @At("HEAD"), cancellable = true) - private void cc_onGetRenderSectionAt(BlockPos pos, CallbackInfoReturnable cir) { - if (!((CanBeCubic) level).cc_isCubic()) return; - int x = Mth.positiveModulo(Mth.floorDiv(pos.getX(), 16), this.sectionGridSizeX); - int y = Mth.positiveModulo(Mth.floorDiv(pos.getY(), 16), this.sectionGridSizeY); - int z = Mth.positiveModulo(Mth.floorDiv(pos.getZ(), 16), this.sectionGridSizeZ); - cir.setReturnValue(this.sections[this.getSectionIndex(x, y, z)]); - } - - // TODO can we do this with dasm + mixin? probably too messy - // I don't really understand the coordinate maths here, but we replicate it for the Y axis - @Override public void cc_repositionCamera(double viewEntityX, double viewEntityY, double viewEntityZ) { - int ceilX = Mth.ceil(viewEntityX); - int ceilY = Mth.ceil(viewEntityY); - int ceilZ = Mth.ceil(viewEntityZ); - - for(int sectionX = 0; sectionX < this.sectionGridSizeX; ++sectionX) { - int maxX = this.sectionGridSizeX * 16; - int i1 = ceilX - 8 - maxX / 2; - int originX = i1 + Math.floorMod(sectionX * 16 - i1, maxX); - - for(int sectionZ = 0; sectionZ < this.sectionGridSizeZ; ++sectionZ) { - int maxZ = this.sectionGridSizeZ * 16; - int i2 = ceilZ - 8 - maxZ / 2; - int originZ = i2 + Math.floorMod(sectionZ * 16 - i2, maxZ); - - for(int sectionY = 0; sectionY < this.sectionGridSizeY; ++sectionY) { - int maxY = this.sectionGridSizeY * 16; - int i3 = ceilY - 8 - maxY / 2; - int originY = i3 + Math.floorMod(sectionY * 16 - i3, maxY); - - SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection = this.sections[this.getSectionIndex(sectionX, sectionY, sectionZ)]; - BlockPos blockpos = sectionrenderdispatcher$rendersection.getOrigin(); - if (originX != blockpos.getX() || originY != blockpos.getY() || originZ != blockpos.getZ()) { - sectionrenderdispatcher$rendersection.setOrigin(originX, originY, originZ); - } - } - } - } - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/MixinRenderRegionCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/MixinRenderRegionCache.java deleted file mode 100644 index 1868e400..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/MixinRenderRegionCache.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer.chunk; - -import javax.annotation.Nullable; - -import io.github.opencubicchunks.cc_core.api.CubePos; -import io.github.opencubicchunks.cc_core.api.CubicConstants; -import io.github.opencubicchunks.cc_core.utils.Coords; -import io.github.opencubicchunks.cubicchunks.client.renderer.cube.CubicRenderRegionCache; -import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCube; -import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCubeRegion; -import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderRegionCacheCubeInfo; -import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import net.minecraft.client.renderer.chunk.RenderRegionCache; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(RenderRegionCache.class) -public class MixinRenderRegionCache implements CubicRenderRegionCache { - @Shadow private final Long2ObjectMap chunkInfoCache = new Long2ObjectOpenHashMap<>(); - - // TODO can we possibly do this with DASM + mixin? probably not? - @Override @Nullable public RenderCubeRegion cc_createRegion(Level level, BlockPos start, BlockPos end, int padding, boolean nullForEmpty) { - var cubicLevel = ((CubicLevel) level); - int cubeStartX = Coords.blockToCube(start.getX() - padding); - int cubeStartY = Coords.blockToCube(start.getY() - padding); - int cubeStartZ = Coords.blockToCube(start.getZ() - padding); - int cubeEndX = Coords.blockToCube(end.getX() + padding); - int cubeEndY = Coords.blockToCube(end.getY() + padding); - int cubeEndZ = Coords.blockToCube(end.getZ() + padding); - RenderRegionCacheCubeInfo[][][] arenderregioncache$chunkinfo = new RenderRegionCacheCubeInfo[cubeEndX - cubeStartX + 1][cubeEndY - cubeStartY + 1][cubeEndZ - cubeStartZ + 1]; - - for(int cubeX = cubeStartX; cubeX <= cubeEndX; ++cubeX) { - for(int cubeY = cubeStartY; cubeY <= cubeEndY; ++cubeY) { - for(int cubeZ = cubeStartZ; cubeZ <= cubeEndZ; ++cubeZ) { - arenderregioncache$chunkinfo[cubeX - cubeStartX][cubeY - cubeStartY][cubeZ - cubeStartZ] = this.chunkInfoCache - .computeIfAbsent( - CubePos.asLong(cubeX, cubeY, cubeZ), - cubePosLong -> new RenderRegionCacheCubeInfo(cubicLevel.cc_getCube(CubePos.extractX(cubePosLong), CubePos.extractY(cubePosLong), CubePos.extractZ(cubePosLong))) - ); - } - } - } - - if (nullForEmpty && cc_isAllEmpty(start, end, cubeStartX, cubeStartY, cubeStartZ, arenderregioncache$chunkinfo)) { - return null; - } else { - RenderCube[][][] arenderchunk = new RenderCube[cubeEndX - cubeStartX + 1][cubeEndY - cubeStartY + 1][cubeEndZ - cubeStartZ + 1]; - - for(int x = cubeStartX; x <= cubeEndX; ++x) { - for(int y = cubeStartY; y <= cubeEndY; ++y) { - for(int z = cubeStartZ; z <= cubeEndZ; ++z) { - arenderchunk[x - cubeStartX][y - cubeStartY][z - cubeStartZ] = arenderregioncache$chunkinfo[x - cubeStartX][y - cubeStartY][z - cubeStartZ].renderCube(); - } - } - } - - var maxSection = CubicConstants.DIAMETER_IN_SECTIONS - 1; - - var modelDataManager = level.getModelDataManager().snapshotSectionRegion( - Coords.cubeToSection(cubeStartX, 0), Coords.cubeToSection(cubeStartY, 0), Coords.cubeToSection(cubeStartZ, 0), - Coords.cubeToSection(cubeEndX, maxSection), Coords.cubeToSection(cubeEndY, maxSection), Coords.cubeToSection(cubeEndZ, maxSection) - ); - return new RenderCubeRegion(level, cubeStartX, cubeStartY, cubeStartZ, arenderchunk, modelDataManager); - } - } - - private static boolean cc_isAllEmpty(BlockPos start, BlockPos end, int startX, int startY, int startZ, RenderRegionCacheCubeInfo[][][] infos) { - return false; - // TODO this seems to only be used as an optimization to avoid rendering empty sections so we probably don't need to implement it immediately - // instead of using vanilla logic, probably just want to individually check that every section in the bounds is empty - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/MixinSectionRenderDispatcher$RenderSection.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/MixinSectionRenderDispatcher$RenderSection.java deleted file mode 100644 index 5fdac03d..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/MixinSectionRenderDispatcher$RenderSection.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer.chunk; - -import javax.annotation.Nullable; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import io.github.opencubicchunks.cc_core.api.CubicConstants; -import io.github.opencubicchunks.cc_core.utils.Coords; -import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.client.renderer.cube.CubicRenderRegionCache; -import io.github.opencubicchunks.cubicchunks.mixin.access.client.SectionRenderDispatcherAccess; -import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; -import net.minecraft.Util; -import net.minecraft.client.renderer.chunk.RenderChunkRegion; -import net.minecraft.client.renderer.chunk.RenderRegionCache; -import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.chunk.ChunkStatus; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(SectionRenderDispatcher.RenderSection.class) -public abstract class MixinSectionRenderDispatcher$RenderSection { - @Shadow @Final SectionRenderDispatcher this$0; - @Shadow @Final BlockPos.MutableBlockPos origin; - @Shadow @Final private BlockPos.MutableBlockPos[] relativeOrigins; - // For neighbor checking, we need to make sure that we offset into neighboring cubes rather than different sections in the same cube - // (Vanilla avoids this problem since chunks are one section wide) - private final BlockPos.MutableBlockPos[] cc_relativeOriginsForNeighborChecks = Util.make(new BlockPos.MutableBlockPos[6], p_294717_ -> { - for(int i = 0; i < p_294717_.length; ++i) { - p_294717_[i] = new BlockPos.MutableBlockPos(); - } - }); - @Shadow protected abstract boolean doesChunkExistAt(BlockPos pos); - - @Inject(method = "setOrigin", at = @At("RETURN")) - private void cc_onSetOrigin(int x, int y, int z, CallbackInfo ci) { - for(Direction direction : Direction.values()) { - this.cc_relativeOriginsForNeighborChecks[direction.ordinal()].set(this.origin).move(direction, CubicConstants.DIAMETER_IN_BLOCKS); - } - } - - @Inject(method = "doesChunkExistAt", at = @At("HEAD"), cancellable = true) - private void cc_onDoesChunkExistAt(BlockPos pos, CallbackInfoReturnable cir) { - if (!((CanBeCubic) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_isCubic()) - return; - cir.setReturnValue(((CubicLevel) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_getCube(Coords.blockToCube(pos.getX()), Coords.blockToCube(pos.getY()), Coords.blockToCube(pos.getZ()), ChunkStatus.FULL, false) != null); - } - - @Inject(method = "hasAllNeighbors", cancellable = true, at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;doesChunkExistAt(Lnet/minecraft/core/BlockPos;)Z")) - private void cc_onHasAllNeighbors(CallbackInfoReturnable cir) { - if (!((CanBeCubic) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_isCubic()) - return; - cir.setReturnValue( - this.doesChunkExistAt(this.cc_relativeOriginsForNeighborChecks[Direction.UP.ordinal()]) - && this.doesChunkExistAt(this.cc_relativeOriginsForNeighborChecks[Direction.DOWN.ordinal()]) - && this.doesChunkExistAt(this.cc_relativeOriginsForNeighborChecks[Direction.WEST.ordinal()]) - && this.doesChunkExistAt(this.cc_relativeOriginsForNeighborChecks[Direction.NORTH.ordinal()]) - && this.doesChunkExistAt(this.cc_relativeOriginsForNeighborChecks[Direction.EAST.ordinal()]) - && this.doesChunkExistAt(this.cc_relativeOriginsForNeighborChecks[Direction.SOUTH.ordinal()]) - ); - } - - @WrapOperation(method = "createCompileTask", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/RenderRegionCache;createRegion(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/client/renderer/chunk/RenderChunkRegion;")) - @Nullable private RenderChunkRegion cc_onCreateCompileTask_createRegion(RenderRegionCache instance, Level level, BlockPos pos1, BlockPos pos2, int i, boolean bool, - Operation original) { - if (!((CanBeCubic) ((SectionRenderDispatcherAccess) this$0).cc_getLevel()).cc_isCubic()) - return original.call(instance, level, pos1, pos2, i, bool); - return ((CubicRenderRegionCache) instance).cc_createRegion(level, pos1, pos2, i, bool); - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/package-info.java deleted file mode 100644 index 139e2d5e..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/chunk/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer.chunk; - -import javax.annotation.ParametersAreNonnullByDefault; - -import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/movetoforgesourcesetlater/MixinCCCommonHooks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/movetoforgesourcesetlater/MixinCCCommonHooks.java new file mode 100644 index 00000000..9950c383 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/movetoforgesourcesetlater/MixinCCCommonHooks.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.movetoforgesourcesetlater; + +import io.github.opencubicchunks.cubicchunks.movetoforgesourcesetlater.CCCommonHooks; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CCCommonHooks.class) +public class MixinCCCommonHooks { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/movetoforgesourcesetlater/MixinGenerationChunkHolder_Forge.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/movetoforgesourcesetlater/MixinGenerationChunkHolder_Forge.java new file mode 100644 index 00000000..d19d51d5 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/movetoforgesourcesetlater/MixinGenerationChunkHolder_Forge.java @@ -0,0 +1,41 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.movetoforgesourcesetlater; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Dasm(GlobalSet.class) +@Mixin(GenerationChunkHolder.class) +public class MixinGenerationChunkHolder_Forge { + protected CubePos cc_cubePos; + @Shadow public LevelChunk currentlyLoading; + + // Corresponds to field added by Forge + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(GenerationChunkHolder.class), field = @FieldSig(name = "currentlyLoading", type = @Ref(LevelChunk.class))) + public LevelCube cc_currentlyLoadingCube; + + public LevelClo cc_getCurrentlyLoading() { + if (cc_cubePos != null) { + return cc_currentlyLoadingCube; + } + return (LevelClo) currentlyLoading; + } + + public void cc_setCurrentlyLoading(LevelClo clo) { + if (cc_cubePos != null) { + cc_currentlyLoadingCube = (LevelCube) clo; + } else { + currentlyLoading = (LevelChunk) clo; + } + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java index 9c736476..516c013f 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java @@ -3,24 +3,21 @@ import java.util.function.Predicate; import java.util.stream.Stream; -import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalRef; import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.api.CubicConstants; import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cc_core.world.SpawnPlaceFinder; -import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.TicketType; import net.minecraft.tags.BlockTags; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.GameRules; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.storage.ServerLevelData; import org.spongepowered.asm.mixin.Mixin; @@ -36,8 +33,7 @@ public abstract class MixinMinecraftServer { @Shadow public abstract ServerLevel overworld(); - // TODO P2 :: This value is dynamic in 1.21, we will need to revisit this - private static final int VANILLA_DEFAULT_SPAWN_CHUNK_RADIUS = 11; + @Shadow public abstract GameRules getGameRules(); // setInitialSpawn // We replace the ChunkPos spawn position with a CubePos spawn position and reuse it later to get the world position. @@ -79,38 +75,15 @@ private static int cc_replaceGetHeightWithSpawnPlaceFinder(ServerLevel serverLev return original.call(serverLevel, heightmapType, x, z); } - // prepareLevels - // This mixin is copied from CC2. It fills in a spawnRadiusRef that is used to determine how many cubes we need to generate for spawn to be ready. - @WrapWithCondition(method = "prepareLevels", at = @At(value = "INVOKE", - target = "Lnet/minecraft/server/level/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - private boolean cc_replaceAddRegionTicketInPrepareLevels(ServerChunkCache serverChunkCache, TicketType ticketType, ChunkPos chunkPos, int originalSpawnRadius, T unit, - @Share("spawnRadius") LocalRef spawnRadiusRef) { - if (((CanBeCubic) serverChunkCache).cc_isCubic()) { - int spawnRadius = Coords.sectionToCube(VANILLA_DEFAULT_SPAWN_CHUNK_RADIUS); - spawnRadiusRef.set(spawnRadius); - spawnRadius++; // TODO there's an off-by-one error somewhere that means that we need to add one here to generate the actual correct spawn radius - ((ServerCubeCache)serverChunkCache).cc_addRegionTicket(ticketType, CloPos.cube(overworld().getSharedSpawnPos()), spawnRadius, unit); - return false; + @WrapOperation(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;square(I)I")) + private int cc_onPrepareLevels_computeTickingGeneratedCount(int value, Operation original) { + if (!((CanBeCubic) overworld()).cc_isCubic()) { + return original.call(value); } - - return true; - } - - @ModifyConstant(method = "prepareLevels", constant = @Constant(intValue = 441), require = 1) - private int cc_modifyExpectedNumberOfTickingGenerated(int constant, @Share("spawnRadius") LocalRef spawnRadiusRef) { - if (((CanBeCubic) overworld()).cc_isCubic()) { - // We need to calculate the number of cubes + chunks in the expected radius - int spawnRadius = spawnRadiusRef.get(); - int spawnDiameterCubes = spawnRadius * 2 + 1; - int cubesInRadius = spawnDiameterCubes * spawnDiameterCubes * spawnDiameterCubes; - - int spawnDiameterChunks = Coords.cubeToSection(spawnDiameterCubes, 0); - int chunksInRadius = spawnDiameterChunks * spawnDiameterChunks; - - return cubesInRadius + chunksInRadius; - } - - return constant; + int cubeRadius = Coords.sectionToCubeCeil(this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)); + int cubeDiameter = cubeRadius * 2 + 1; + int chunkDiameter = cubeDiameter * CubicConstants.DIAMETER_IN_SECTIONS; + return cubeDiameter * cubeDiameter * cubeDiameter + chunkDiameter * chunkDiameter; } // Temporary hack to let us unload a world without saving diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkGenerationTask.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkGenerationTask.java new file mode 100644 index 00000000..46a51362 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkGenerationTask.java @@ -0,0 +1,285 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import javax.annotation.Nullable; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.annotation.Public; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.mixin.access.common.GenerationChunkHolderAccess; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; +import io.github.opencubicchunks.cubicchunks.server.level.CloGenerationTask; +import io.github.opencubicchunks.cubicchunks.server.level.GeneratingCubeMap; +import io.github.opencubicchunks.cubicchunks.server.level.GenerationCloHolder; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubePyramid; +import net.minecraft.server.level.ChunkGenerationTask; +import net.minecraft.server.level.GeneratingChunkMap; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.util.StaticCache2D; +import net.minecraft.util.profiling.Profiler; +import net.minecraft.util.profiling.Zone; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.status.ChunkDependencies; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * {@link ChunkGenerationTask} handles loading a given chunk at a given {@link ChunkStatus}, including generation if required, and loading neighboring chunks as required. + *

+ * We modify it to support cube loading as well, loading both neighboring cubes and chunks as required, and preserving chunk-cube load order invariants + * (cube status never exceeds the status of any chunks intersecting it) + */ +@Dasm(GlobalSet.class) +@Mixin(ChunkGenerationTask.class) +public abstract class MixinChunkGenerationTask implements CloGenerationTask { + @Shadow @Final private ChunkPos pos; + @Shadow @Final private GeneratingChunkMap chunkMap; + @Shadow @Nullable private ChunkStatus scheduledStatus; + @Shadow @Final public ChunkStatus targetStatus; + @Shadow private volatile boolean markedForCancellation; + @Shadow @Final private StaticCache2D cache; + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkGenerationTask.class), field = @FieldSig(type = @Ref(ChunkPos.class), name = "pos")) + private CubePos cc_cubePos; + // scheduledChunkStatus must be one status higher than the scheduled status for cubes until the target status is reached, to ensure load order invariants are preserved + // we use the vanilla field for cube status, since that is the status of the actual cube that is being generated + @Nullable public ChunkStatus cc_scheduledChunkStatus; + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkGenerationTask.class), field = @FieldSig(type = @Ref(StaticCache2D.class), name = "cache")) + private StaticCache3D cc_cubeCache; + + private GeneratingCubeMap cc_getGeneratingCubeMap() { + return ((GeneratingCubeMap) chunkMap); + } + + @Override public CloPos cc_getCloPos() { + if (cc_cubePos != null) { + return CloPos.cube(cc_cubePos); + } + return CloPos.chunk(pos); + } + + /** + * Factory method to create a {@code ChunkGenerationTask} for a cube. + */ + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkGenerationTask.class), method = @MethodSig("create(Lnet/minecraft/server/level/GeneratingChunkMap;Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/server/level/ChunkGenerationTask;")) + @Public private static ChunkGenerationTask cc_createCubeGenerationTask(GeneratingChunkMap chunkMap, ChunkStatus targetStatus, CubePos pos) { + int cubeRadius = CubePyramid.CC_GENERATION_PYRAMID_CUBES.getStepTo(targetStatus).getAccumulatedRadiusOf(ChunkStatus.EMPTY); + int cubeDiameter = cubeRadius * 2 + 1; + int chunkDiameter = cubeDiameter * CubicConstants.DIAMETER_IN_SECTIONS; + // We directly use the StaticCache2D constructor, as the `create` factory method only allows for odd dimensions, and `chunkDiameter` is even (for cube sizes greater than 16) + StaticCache2D staticcache2d = new StaticCache2D<>( + Coords.cubeToSection(pos.getX() - cubeRadius, 0), Coords.cubeToSection(pos.getZ() - cubeRadius, 0), chunkDiameter, chunkDiameter, (x, z) -> chunkMap.acquireGeneration(ChunkPos.asLong(x, z)) + ); + var chunkGenerationTask = new ChunkGenerationTask(chunkMap, targetStatus, null, staticcache2d); + ((MixinChunkGenerationTask) (Object) chunkGenerationTask).cc_cubePos = pos; + ((MixinChunkGenerationTask) (Object) chunkGenerationTask).cc_cubeCache = StaticCache3D.create( + pos.getX(), pos.getY(), pos.getZ(), cubeRadius, (x, y, z) -> chunkMap.acquireGeneration(CubePos.asLong(x, y, z)) + ); + return chunkGenerationTask; + } + + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkGenerationTask.class), method = @MethodSig("create(Lnet/minecraft/server/level/GeneratingChunkMap;Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/server/level/ChunkGenerationTask;")) + @Public private static ChunkGenerationTask cc_createCubeGenerationTask(GeneratingChunkMap chunkMap, ChunkStatus targetStatus, CloPos pos) { + if (pos.isCube()) { + return cc_createCubeGenerationTask(chunkMap, targetStatus, pos.cubePos()); + } else { + return create(chunkMap, targetStatus, pos.chunkPos()); + } + } + + @Inject(method = "scheduleNextLayer", at = @At("HEAD"), cancellable = true) + private void cc_onScheduleNextLayer(CallbackInfo ci) { + if (cc_cubePos != null) { + ci.cancel(); + cc_scheduleNextLayer(); + } + } + + /** + * Cube equivalent to {@code scheduleNextLayer}. + * Loads chunks one status higher than cubes until chunks have reached the target status, as otherwise cubes could reach a status before their intersecting chunks do. + */ + private void cc_scheduleNextLayer() { + ChunkStatus nextChunkStatus; + ChunkStatus nextCubeStatus = null; + // First two branches of this `if` are equivalent to the vanilla method; we are loading chunks at EMPTY so cubes are not involved yet + if (this.cc_scheduledChunkStatus == null) { + nextChunkStatus = ChunkStatus.EMPTY; + } else if (!this.needsGeneration && this.cc_scheduledChunkStatus == ChunkStatus.EMPTY && !this.canLoadWithoutGeneration()) { + this.needsGeneration = true; + nextChunkStatus = ChunkStatus.EMPTY; + } else { + // All chunks have reached `cc_scheduledChunkStatus`, so cubes can now be scheduled at that status + nextCubeStatus = this.cc_scheduledChunkStatus; + if (nextCubeStatus.isOrAfter(this.targetStatus)) { + // If nextCubeStatus is at or after targetStatus, it means chunks have already reached targetStatus and we just need cubes + nextChunkStatus = null; + } else { + // Simultaneously schedule chunks one status ahead of cubes + nextChunkStatus = ChunkStatus.getStatusList().get(this.cc_scheduledChunkStatus.getIndex() + 1); + } + } + + this.cc_scheduleLayer(nextChunkStatus, nextCubeStatus, this.needsGeneration); + this.scheduledStatus = nextCubeStatus; + if (nextChunkStatus != null) { // If nextChunkStatus is null, the scheduled chunk status did not change + this.cc_scheduledChunkStatus = nextChunkStatus; + } + } + + /** + * When loading a cube, get the center cube instead of chunk, and call {@link GeneratingChunkMap#releaseGeneration} for cubes as well + */ + @Inject(method = "releaseClaim", at = @At("HEAD"), cancellable = true) + private void cc_onReleaseClaim(CallbackInfo ci) { + if (cc_cubePos == null) { + return; + } + ci.cancel(); + GenerationChunkHolder generationchunkholder = this.cc_cubeCache.get(this.cc_cubePos.getX(), this.cc_cubePos.getY(), this.cc_cubePos.getZ()); + ((GenerationChunkHolderAccess) generationchunkholder).cc_invokeRemoveTask(((ChunkGenerationTask) (Object) this)); + this.cache.forEach(this.chunkMap::releaseGeneration); + this.cc_cubeCache.forEach(this.chunkMap::releaseGeneration); + } + + /** + * When loading a cube, check cube dependencies as well when determining if generation is required + */ + @Inject(method = "canLoadWithoutGeneration", at = @At("HEAD"), cancellable = true) + private void cc_onCanLoadWithoutGeneration(CallbackInfoReturnable cir) { + if (cc_cubePos == null) { + return; + } + if (this.targetStatus == ChunkStatus.EMPTY) { + cir.setReturnValue(true); + } else { + ChunkStatus currentCubeStatus = this.cc_cubeCache.get(this.cc_cubePos.getX(), this.cc_cubePos.getY(), this.cc_cubePos.getZ()).getPersistedStatus(); + if (currentCubeStatus != null && !currentCubeStatus.isBefore(this.targetStatus)) { + ChunkDependencies cubeDependencies = CubePyramid.CC_LOADING_PYRAMID_CUBES.getStepTo(this.targetStatus).accumulatedDependencies(); + int cubeRadius = cubeDependencies.getRadius(); + + for (int cubeX = this.cc_cubePos.getX() - cubeRadius; cubeX <= this.cc_cubePos.getX() + cubeRadius; cubeX++) { + for (int cubeZ = this.cc_cubePos.getZ() - cubeRadius; cubeZ <= this.cc_cubePos.getZ() + cubeRadius; cubeZ++) { + for (int cubeY = this.cc_cubePos.getY() - cubeRadius; cubeY <= this.cc_cubePos.getY() + cubeRadius; cubeY++) { + int distance = this.cc_cubePos.getChessboardDistance(cubeX, cubeY, cubeZ); + ChunkStatus dependencyRequiredStatus = cubeDependencies.get(distance); + ChunkStatus dependencyCurrentStatus = this.cc_cubeCache.get(cubeX, cubeY, cubeZ).getPersistedStatus(); + if (dependencyCurrentStatus == null || dependencyCurrentStatus.isBefore(dependencyRequiredStatus)) { + cir.setReturnValue(false); + return; + } + } + // Chunk required status is the highest status of all cubes, which occurs when cubeY is equal to this.cc_cubePos.y + int chunkDistanceInCubes = this.cc_cubePos.getChessboardDistance(cubeX, this.cc_cubePos.getY(), cubeZ); + ChunkStatus dependencyRequiredStatus = cubeDependencies.get(chunkDistanceInCubes); + for (int dx = 0; dx < CubicConstants.DIAMETER_IN_SECTIONS; dx++) { + for (int dz = 0; dz < CubicConstants.DIAMETER_IN_SECTIONS; dz++) { + int chunkX = Coords.cubeToSection(cubeX, dx); + int chunkZ = Coords.cubeToSection(cubeZ, dz); + ChunkStatus dependencyCurrentStatus = this.cache.get(chunkX, chunkZ).getPersistedStatus(); + if (dependencyCurrentStatus == null || dependencyCurrentStatus.isBefore(dependencyRequiredStatus)) { + cir.setReturnValue(false); + return; + } + } + } + } + } + + cir.setReturnValue(true); + } else { + cir.setReturnValue(false); + } + } + } + + @Inject(method = "getCenter", at = @At("HEAD"), cancellable = true) + private void cc_onGetCenter(CallbackInfoReturnable cir) { + if (cc_cubePos != null) cir.setReturnValue(this.cc_cubeCache.get(this.cc_cubePos.getX(), this.cc_cubePos.getY(), this.cc_cubePos.getZ())); + } + + /** + * Cubic equivalent of {@code scheduleLayer}; schedules both cubes and chunks at given statuses (Chunk status will be higher to preserve load order invariants) + */ + // TODO this could be two methods I guess? does it matter? + private void cc_scheduleLayer(@Nullable ChunkStatus chunkStatus, @Nullable ChunkStatus cubeStatus, boolean needsGeneration) { + try (Zone zone = Profiler.get().zone("scheduleLayer")) { + zone.addText(() -> String.format("Chunk: %s, Cube: %s", chunkStatus == null ? "null" : chunkStatus.getName(), cubeStatus == null ? "null" : cubeStatus.getName())); + if (cubeStatus != null) { + int cubeRadius = this.cc_getCubeRadiusForLayer(cubeStatus, needsGeneration); + for (int cubeX = this.cc_cubePos.getX() - cubeRadius; cubeX <= this.cc_cubePos.getX() + cubeRadius; cubeX++) { + for (int cubeZ = this.cc_cubePos.getZ() - cubeRadius; cubeZ <= this.cc_cubePos.getZ() + cubeRadius; cubeZ++) { + for (int cubeY = this.cc_cubePos.getY() - cubeRadius; cubeY <= this.cc_cubePos.getY() + cubeRadius; cubeY++) { + GenerationChunkHolder generationchunkholder = this.cc_cubeCache.get(cubeX, cubeY, cubeZ); + if (this.markedForCancellation || !this.scheduleChunkInLayer(cubeStatus, needsGeneration, generationchunkholder)) { + return; + } + } + } + } + } + if (chunkStatus != null) { + int cubeRadius = this.cc_getCubeRadiusForLayer(chunkStatus, needsGeneration); + for (int cubeX = this.cc_cubePos.getX() - cubeRadius; cubeX <= this.cc_cubePos.getX() + cubeRadius; cubeX++) { + for (int cubeZ = this.cc_cubePos.getZ() - cubeRadius; cubeZ <= this.cc_cubePos.getZ() + cubeRadius; cubeZ++) { + for (int dx = 0; dx < CubicConstants.DIAMETER_IN_SECTIONS; dx++) { + for (int dz = 0; dz < CubicConstants.DIAMETER_IN_SECTIONS; dz++) { + int chunkX = Coords.cubeToSection(cubeX, dx); + int chunkZ = Coords.cubeToSection(cubeZ, dz); + GenerationChunkHolder generationchunkholder = this.cache.get(chunkX, chunkZ); + if (this.markedForCancellation || !this.scheduleChunkInLayer(chunkStatus, needsGeneration, generationchunkholder)) { + return; + } + } + } + } + } + } + } + } + + @Inject(method = "scheduleLayer", at = @At("HEAD")) + private void cc_onScheduleLayer(ChunkStatus status, boolean needsGeneration, CallbackInfo ci) { + if (cc_cubePos == null) return; + throw new IllegalStateException("shouldn't call vanilla scheduleLayer for cube generation task"); + } + + @TransformFromMethod(useRedirectSets = ChunkToCubeSet.class, owner = @Ref(ChunkGenerationTask.class), value = @MethodSig("getRadiusForLayer(Lnet/minecraft/world/level/chunk/status/ChunkStatus;Z)I")) + private native int cc_getCubeRadiusForLayer(ChunkStatus status, boolean needsGeneration); + + @Shadow protected abstract boolean scheduleChunkInLayer(ChunkStatus status, boolean needsGeneration, GenerationChunkHolder chunk); + + @Shadow public static ChunkGenerationTask create(GeneratingChunkMap chunkMap, ChunkStatus targetStatus, ChunkPos pos) { + return null; + } + + @Shadow private boolean needsGeneration; + + @Shadow protected abstract boolean canLoadWithoutGeneration(); + + @Inject(method = "scheduleChunkInLayer", at = @At("HEAD"), cancellable = true) + private void cc_onScheduleChunkInLayer(ChunkStatus status, boolean needsGeneration, GenerationChunkHolder chunk, CallbackInfoReturnable cir) { + if (((GenerationCloHolder) chunk).cc_getCubePos() != null) { + cir.setReturnValue(cc_scheduleCubeInLayer(status, needsGeneration, chunk)); + } + } + + @TransformFromMethod(useRedirectSets = ChunkToCubeSet.class, owner = @Ref(ChunkGenerationTask.class), value = @MethodSig("scheduleChunkInLayer(Lnet/minecraft/world/level/chunk/status/ChunkStatus;ZLnet/minecraft/server/level/GenerationChunkHolder;)Z")) + private native boolean cc_scheduleCubeInLayer(ChunkStatus status, boolean needsGeneration, GenerationChunkHolder chunk); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java index 01d22100..2203082c 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java @@ -1,16 +1,12 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicReferenceArray; -import java.util.function.BiConsumer; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; import javax.annotation.Nullable; -import com.llamalad7.mixinextras.sugar.Local; -import com.mojang.datafixers.util.Either; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import io.github.notstirred.dasm.api.annotations.Dasm; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; @@ -19,248 +15,178 @@ import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.server.level.CloHolder; +import io.github.opencubicchunks.cubicchunks.server.level.CubeHolder; +import io.github.opencubicchunks.cubicchunks.server.level.CubicChunkMap; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import it.unimi.dsi.fastutil.shorts.ShortSet; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; -import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; -import net.minecraft.world.level.chunk.LevelChunkSection; -import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; import org.spongepowered.asm.mixin.Dynamic; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; /** - * The vanilla {@link ChunkHolder} class wraps completable futures for different statuses (load levels) of a single chunk and handles logic for loading/unloading that chunk, as well as broadcasting updates to clients. - * This mixin adds cubic chunks equivalents for methods where necessary, to allow ChunkHolder to dynamically wrap either a chunk or a cube (i.e. a CLO). + * The vanilla {@link ChunkHolder} class extends {@link GenerationChunkHolder} to have methods for getting a fully loaded chunk, and handle saving dependencies and broadcasting updates to clients. + * This mixin adds cubic chunks equivalents for methods where necessary, to allow GenerationChunkHolder to dynamically wrap either a chunk or a cube (i.e. a CLO). */ -@Dasm(ChunkToCloSet.class) +@Dasm(ChunkToCubeSet.class) @Mixin(ChunkHolder.class) -public abstract class MixinChunkHolder implements CloHolder { - @Shadow @Final private static List CHUNK_STATUSES; - - private boolean cc_isCubic; - - @AddFieldToSets(sets = GlobalSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "pos", type = @Ref(ChunkPos.class))) - private CloPos cc_cloPos; - - @AddFieldToSets(sets = GlobalSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "onLevelChange", type = @Ref(ChunkHolder.LevelChangeListener.class))) - private final CloHolder.LevelChangeListener cc_onLevelChange; - @AddFieldToSets(sets = GlobalSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "playerProvider", type = @Ref(ChunkHolder.PlayerProvider.class))) - private final CloHolder.PlayerProvider cc_playerProvider; - - /** - * Listeners for each ChunkStatus, that are notified when this CloHolder has reached that status - */ - private AtomicReferenceArray, Throwable>>> cc_listenerLists; - - @AddMethodToSets(sets = GlobalSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) - @Override public CloPos cc_getPos() { - return cc_cloPos; - } - - @Shadow @Mutable @Final ChunkPos pos; +public abstract class MixinChunkHolder extends MixinGenerationChunkHolder implements CloHolder, CubeHolder { @Shadow private boolean hasChangedSections; - @Shadow @Final private final ShortSet[] changedBlocksPerSection; + @Shadow @Final private ShortSet[] changedBlocksPerSection; - @Shadow protected abstract void broadcastBlockEntityIfNeeded(List players, Level level, BlockPos pos, BlockState state); + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(type = @Ref(ChunkHolder.LevelChangeListener.class), name = "onLevelChange")) + private final LevelChangeListener cc_onLevelChange; - @Shadow protected abstract void broadcast(List players, Packet packet); + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(type = @Ref(ChunkHolder.PlayerProvider.class), name = "playerProvider")) + private final PlayerProvider cc_playerProvider; - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("(Lnet/minecraft/world/level/ChunkPos;ILnet/minecraft/world/level/LevelHeightAccessor;Lnet/minecraft/world/level/lighting/LevelLightEngine;" - + "Lnet/minecraft/server/level/ChunkHolder$LevelChangeListener;Lnet/minecraft/server/level/ChunkHolder$PlayerProvider;)V")) + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkHolder.class), value = @MethodSig("(Lnet/minecraft/world/level/ChunkPos;ILnet/minecraft/world/level/LevelHeightAccessor;Lnet/minecraft/world/level/lighting/LevelLightEngine;Lnet/minecraft/server/level/ChunkHolder$LevelChangeListener;Lnet/minecraft/server/level/ChunkHolder$PlayerProvider;)V")) public MixinChunkHolder() { - throw new IllegalStateException("dasm failed to apply"); + throw new DasmFailedToApply(); } - @Dynamic @Inject(at = @At("RETURN"), method = "cc_dasm$__init__(Lio/github/opencubicchunks/cc_core/world/level/CloPos;ILnet/minecraft/world/level/LevelHeightAccessor;" - + "Lnet/minecraft/world/level/lighting/LevelLightEngine;Lio/github/opencubicchunks/cubicchunks/server/level/CloHolder$LevelChangeListener;" - + "Lio/github/opencubicchunks/cubicchunks/server/level/CloHolder$PlayerProvider;)V") - private void cc_onCcInit(CloPos cloPos, - int ticketLevel, - LevelHeightAccessor levelHeightAccessor, - LevelLightEngine lightEngine, - CloHolder.LevelChangeListener onLevelChange, - CloHolder.PlayerProvider playerProvider, - CallbackInfo ci) { - // TODO redirect changedBlocksPerSection construction for chunks - cc_isCubic = true; - if (cloPos.isChunk()) this.pos = cloPos.chunkPos(); - cc_listenerLists = new AtomicReferenceArray<>(CHUNK_STATUSES.size()); - } + @Shadow @Nullable public abstract LevelChunk getTickingChunk(); - @Override public void cc_addCloStatusListener(ChunkStatus status, BiConsumer, Throwable> consumer, ChunkMap chunkMap) { - CompletableFuture> future = cc_getOrScheduleFuture(status, chunkMap); + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkHolder.class), value = @MethodSig("getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public native LevelCube cc_getTickingCube(); - if (future.isDone()) { - consumer.accept(future.getNow(null), null); - } else { - List, Throwable>> listenerList = this.cc_listenerLists.get(status.getIndex()); - if (listenerList == null) { - final ArrayList, Throwable>> listeners = new ArrayList<>(); - future.whenComplete((either, throwable) -> { - for (BiConsumer, Throwable> listener : listeners) { - listener.accept(either, throwable); - } - listeners.clear(); - listeners.trimToSize(); - }); - this.cc_listenerLists.set(status.getIndex(), listeners); - listenerList = listeners; - } - - listenerList.add(consumer); + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public LevelClo cc_getTickingClo() { + if (cc_cubePos != null) { + return cc_getTickingCube(); } + return (LevelClo) getTickingChunk(); } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) - @Nullable public native LevelClo cc_getTickingChunk(); - - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("getChunkToSend()Lnet/minecraft/world/level/chunk/LevelChunk;")) - @Nullable public native LevelClo cc_getChunkToSend(); + @Shadow @Nullable public abstract LevelChunk getChunkToSend(); - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("getFullChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) - @Nullable public native LevelClo cc_getFullChunk(); + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkHolder.class), value = @MethodSig("getChunkToSend()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public native LevelCube cc_getCubeToSend(); - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("getLastAvailable()Lnet/minecraft/world/level/chunk/ChunkAccess;")) - @Nullable public native CloAccess cc_getLastAvailable(); - - // dasm + mixin - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("blockChanged(Lnet/minecraft/core/BlockPos;)V")) - public native void cc_blockChanged(BlockPos pos); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(ChunkHolder.class), value = @MethodSig("getChunkToSend()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public LevelClo cc_getCloToSend() { + if (cc_cubePos != null) { + return cc_getCubeToSend(); + } + return (LevelClo) getChunkToSend(); + } - /** - * Only handle block changes for cubes, as this should not be tracked on columns - */ - @Dynamic @Inject(method = "cc_dasm$cc_blockChanged", - at = @At(value = "INVOKE_ASSIGN", shift = At.Shift.AFTER, - target = "Lnet/minecraft/server/level/ChunkHolder;cc_getTickingChunk()Lio/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo;" - ), - cancellable = true) - private void cc_blockChanged_checkCubic(BlockPos pos, CallbackInfo ci, @Local LevelClo clo) { - if (clo instanceof ChunkAccess) ci.cancel(); + @Inject(method = "blockChanged", at = @At("HEAD"), cancellable = true) + public void cc_onBlockChanged(BlockPos pos, CallbackInfoReturnable cir) { + if (cc_cubePos != null) { + cir.setReturnValue(cc_blockChanged(pos)); + } } - /** - * Redirect to use cube section indexing instead of chunk section indexing - */ - // This can't be done using purely dasm because the original getSectionIndex call takes a y coordinate, whereas we need the full blockpos. + // region [cc_blockChanged dasm + mixin] + @TransformFromMethod(@MethodSig("blockChanged(Lnet/minecraft/core/BlockPos;)Z")) + private native boolean cc_blockChanged(BlockPos pos); + @Dynamic @Redirect(method = "cc_dasm$cc_blockChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/LevelHeightAccessor;getSectionIndex(I)I")) - private int cc_blockChanged_sectionIndex(LevelHeightAccessor instance, int y, BlockPos pos) { - return Coords.blockToIndex(pos); + private int cc_onBlockChanged_sectionIndex(LevelHeightAccessor instance, int y, BlockPos pos) { + return Coords.sectionToIndex(Coords.blockToSection(pos.getX()), Coords.blockToSection(pos.getY()), Coords.blockToSection(pos.getZ())); } + // endregion - // We want a different signature (see below); can't automatically redirect this one - @Inject(method = "sectionLightChanged", at = @At("HEAD")) - private void cc_onSectionLightChanged(LightLayer type, int sectionY, CallbackInfo ci) { - // We should be calling the cubic signature instead - assert !cc_isCubic; + @Inject(method = "sectionLightChanged", at = @At("HEAD"), cancellable = true) + public void cc_onSectionLightChanged(LightLayer lightLayer, int sectionY, CallbackInfoReturnable cir) { + if (cc_cubePos != null) { + cir.setReturnValue(false); // TODO (P2) lighting + } } - @AddMethodToSets(sets = GlobalSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("sectionLightChanged(Lnet/minecraft/world/level/LightLayer;I)V")) - public void cc_sectionLightChanged(LightLayer type, int sectionY) { - // TODO (P2) lighting - } + @Shadow public abstract void broadcastChanges(LevelChunk chunk); - @AddMethodToSets(sets = GlobalSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("broadcastChanges(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) - public void cc_broadcastChanges(LevelClo clo) { - // TODO (P2) also handle lighting - see vanilla method - // TODO seems like this should only run for cubes; is that correct? - if (this.hasChangedSections && clo instanceof LevelCube cube) { - Level level = cube.getLevel(); + // region [cc_broadcastCubeChanges dasm + mixin] + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkHolder.class), value = @MethodSig("broadcastChanges(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) + public native void cc_broadcastCubeChanges(LevelCube cube); + // TODO (P2) lighting - ClientboundLightUpdatePacket branch is currently never reached; once we have lighting it will have to be a CC packet, and this.broadcast will need to redirect to a CC method - List list1 = this.cc_playerProvider.getPlayers(this.cc_cloPos, false); + @Dynamic @Redirect(method = "cc_dasm$cc_broadcastCubeChanges", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/LevelHeightAccessor;getSectionYFromSectionIndex(I)I")) + private int cc_onBroadcastCubeChanges_indexToSectionY(LevelHeightAccessor instance, int sectionIndex) { + // The vanilla method uses SectionPos.of(ChunkPos, sectionY), but we want SectionPos.of(CubePos, sectionIndex). + // The easiest way to accomplish this is to turn `getSectionYFromSectionIndex` into a no-op so that we get sectionIndex instead of sectionY. + // (We could do local captures, but it'd be more brittle) + return sectionIndex; + } - for(int j = 0; j < this.changedBlocksPerSection.length; ++j) { - ShortSet shortset = this.changedBlocksPerSection[j]; - if (shortset != null) { - this.changedBlocksPerSection[j] = null; - if (!list1.isEmpty()) { - SectionPos sectionpos = Coords.sectionPosByIndex(cube.cc_getCloPos().cubePos(), j); - if (shortset.size() == 1) { - BlockPos blockpos = sectionpos.relativeToBlockPos(shortset.iterator().nextShort()); - BlockState blockstate = level.getBlockState(blockpos); - this.broadcast(list1, new ClientboundBlockUpdatePacket(blockpos, blockstate)); - this.broadcastBlockEntityIfNeeded(list1, level, blockpos, blockstate); - } else { - LevelChunkSection levelchunksection = cube.getSection(j); - ClientboundSectionBlocksUpdatePacket clientboundsectionblocksupdatepacket = new ClientboundSectionBlocksUpdatePacket( - sectionpos, shortset, levelchunksection - ); - this.broadcast(list1, clientboundsectionblocksupdatepacket); - clientboundsectionblocksupdatepacket.runUpdates( - (p_288761_, p_288762_) -> this.broadcastBlockEntityIfNeeded(list1, level, p_288761_, p_288762_) - ); - } - } - } - } + @Dynamic @Redirect(method = "cc_dasm$cc_broadcastCubeChanges", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/SectionPos;of(Lio/github/opencubicchunks/cc_core/api/CubePos;I)Lnet/minecraft/core/SectionPos;")) + private SectionPos cc_onBroadcastCubeChanges_sectionPos(CubePos cubePos, int sectionIndex) { + return Coords.sectionPosByIndex(cubePos, sectionIndex); + } + // endregion - this.hasChangedSections = false; + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("broadcastChanges(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) + public void cc_broadcastCloChanges(LevelClo clo) { + if (cc_cubePos != null) { + cc_broadcastCubeChanges((LevelCube) clo); + } else { + broadcastChanges(((LevelChunk) clo)); } } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("getOrScheduleFuture(Lnet/minecraft/world/level/chunk/ChunkStatus;Lnet/minecraft/server/level/ChunkMap;)Ljava/util/concurrent/CompletableFuture;")) - @Override public native CompletableFuture> cc_getOrScheduleFuture(ChunkStatus status, ChunkMap map); - - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("addSaveDependency(Ljava/lang/String;Ljava/util/concurrent/CompletableFuture;)V")) - public native void cc_addSaveDependency(String source, CompletableFuture future); - - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("updateChunkToSave(Ljava/util/concurrent/CompletableFuture;Ljava/lang/String;)V")) - private native void cc_updateChunkToSave(CompletableFuture> future, String source); + @WrapOperation(method = { "lambda$scheduleFullChunkPromotion$4", "demoteFullChunk" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;onFullChunkStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/server/level/FullChunkStatus;)V")) + private void cc_onCallChunkMapOnFullChunkStatusChange(ChunkMap instance, ChunkPos chunkPos, FullChunkStatus fullChunkStatus, Operation original) { + if (cc_cubePos != null) { + ((CubicChunkMap) instance).cc_onFullChunkStatusChange(cc_cubePos, fullChunkStatus); + } else { + original.call(instance, chunkPos, fullChunkStatus); + } + } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("scheduleFullChunkPromotion(Lnet/minecraft/server/level/ChunkMap;Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/Executor;Lnet/minecraft/server/level/FullChunkStatus;)V")) - private native void cc_scheduleFullChunkPromotion( - ChunkMap chunkMap, CompletableFuture> future, Executor executor, FullChunkStatus fullChunkStatus - ); + @WrapOperation(method = "updateFutures", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkHolder$LevelChangeListener;onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) + protected void cc_onUpdateFutures_onCallOnLevelChange(ChunkHolder.LevelChangeListener instance, ChunkPos chunkPos, IntSupplier intSupplier, int i, IntConsumer intConsumer, + Operation original) { + if (cc_cubePos != null) { + cc_onLevelChange.onLevelChange(cc_cubePos, intSupplier, i, intConsumer); + } else { + original.call(instance, chunkPos, intSupplier, i, intConsumer); + } + } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("demoteFullChunk(Lnet/minecraft/server/level/ChunkMap;Lnet/minecraft/server/level/FullChunkStatus;)V")) - private native void cc_demoteFullChunk(ChunkMap chunkMap, FullChunkStatus fullChunkStatus); + // TODO dasm inheritance + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getLatestChunk()Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public CubeAccess cc_getLatestCube() { + return super.cc_getLatestCube(); + } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("updateFutures(Lnet/minecraft/server/level/ChunkMap;Ljava/util/concurrent/Executor;)V")) - public native void cc_updateFutures(ChunkMap chunkMap, Executor executor); + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getLatestChunk()Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public CloAccess cc_getLatestClo() { + return super.cc_getLatestClo(); + } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod( - value = @MethodSig("replaceProtoChunk(Lnet/minecraft/world/level/chunk/ImposterProtoChunk;)V")) - public native void cc_replaceProtoChunk(ImposterProtoClo imposter); + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) + @Override public CloPos cc_getCloPos() { + return super.cc_getCloPos(); + } + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getChunkIfPresent(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public CubeAccess cc_getCubeIfPresent(ChunkStatus status) { + return super.cc_getCubeIfPresent(status); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java deleted file mode 100644 index f1e7bfa7..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; - -import io.github.notstirred.dasm.api.annotations.Dasm; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; -import io.github.notstirred.dasm.api.annotations.selector.FieldSig; -import io.github.notstirred.dasm.api.annotations.selector.Ref; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; -import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; -import net.minecraft.server.level.ChunkHolder; -import net.minecraft.world.level.chunk.LevelChunk; -import org.spongepowered.asm.mixin.Mixin; - -// FIXME should be in forge sourceset once tests run against forge -@Dasm(GlobalSet.class) -@Mixin(ChunkHolder.class) -public class MixinChunkHolder_Forge { - // Field added by Forge - @AddFieldToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "currentlyLoading", type = @Ref(LevelChunk.class))) - LevelClo cc_currentlyLoading; - - // getter/setter as a workaround to the field needing to be used as a LevelClo in some places and a LevelCube in others - LevelCube cc_getCurrentlyLoading() { - return cc_currentlyLoading instanceof LevelCube ? (LevelCube) cc_currentlyLoading : null; - } - - void cc_setCurrentlyLoading(LevelCube clo) { - cc_currentlyLoading = clo; - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap$TrackedEntity.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap$TrackedEntity.java index c52b9619..6560dc9d 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap$TrackedEntity.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap$TrackedEntity.java @@ -20,7 +20,7 @@ public abstract class MixinChunkMap$TrackedEntity { @Shadow @Final Entity entity; - // dasm + mixin + //region [cc_updatePlayer dasm + mixin] @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updatePlayer(Lnet/minecraft/server/level/ServerPlayer;)V")) public native void cc_updatePlayer(ServerPlayer player); @@ -29,4 +29,5 @@ private boolean cc_updatePlayer_isChunkTracked(ChunkMap instance, ServerPlayer p // FIXME entity clo position once implemented return false; //((CubicChunkMap) instance).cc_isChunkTracked(player, this.entity.chunkPosition().x, 0, this.entity.chunkPosition().z); } + //endregion } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java index d0c3542d..5827c3cc 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java @@ -1,23 +1,24 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.IntConsumer; import java.util.function.IntFunction; +import java.util.function.IntSupplier; import java.util.function.Supplier; import javax.annotation.Nullable; -import com.google.common.collect.Lists; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.datafixers.DataFixer; -import com.mojang.datafixers.util.Either; import io.github.notstirred.dasm.api.annotations.Dasm; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; @@ -26,6 +27,7 @@ import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.api.CubicConstants; import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cc_core.world.level.CloPos; @@ -33,39 +35,51 @@ import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.storage.MixinChunkStorage; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.SectionPosToCubeSet; import io.github.opencubicchunks.cubicchunks.network.CCClientboundSetCubeCacheCenterPacket; -import io.github.opencubicchunks.cubicchunks.server.level.CloCollectorFuture; +import io.github.opencubicchunks.cubicchunks.server.level.CCServerPlayer; +import io.github.opencubicchunks.cubicchunks.server.level.CloGenerationTask; import io.github.opencubicchunks.cubicchunks.server.level.CloHolder; import io.github.opencubicchunks.cubicchunks.server.level.CloTrackingView; +import io.github.opencubicchunks.cubicchunks.server.level.CubeHolder; import io.github.opencubicchunks.cubicchunks.server.level.CubicChunkMap; +import io.github.opencubicchunks.cubicchunks.server.level.GeneratingCubeMap; import io.github.opencubicchunks.cubicchunks.server.level.progress.CloProgressListener; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; import io.github.opencubicchunks.cubicchunks.world.level.entity.CloStatusUpdateListener; import net.minecraft.ReportedException; import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.Packet; +import net.minecraft.server.level.ChunkGenerationTask; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.util.thread.BlockableEventLoop; -import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.TicketStorage; import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LightChunkGetter; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkType; import net.minecraft.world.level.entity.ChunkStatusUpdateListener; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.network.PacketDistributor; -import org.slf4j.Logger; import org.spongepowered.asm.mixin.Dynamic; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -82,33 +96,32 @@ */ @Dasm(ChunkToCloSet.class) @Mixin(ChunkMap.class) -public abstract class MixinChunkMap extends MixinChunkStorage implements CubicChunkMap { - // TODO maybe don't shadow logger; use our own? - @Shadow @Final private static Logger LOGGER; - - private static final List cc_CHUNK_STATUSES = ChunkStatus.getStatusList(); - +public abstract class MixinChunkMap extends MixinChunkStorage implements GeneratingCubeMap, CubicChunkMap, CubeHolder.PlayerProvider { @Shadow public abstract ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details); @Shadow protected abstract ChunkHolder getUpdatingChunkIfPresent(long aLong); - @Shadow private static boolean isChunkDataValid(CompoundTag tag) { - throw new IllegalStateException("mixin failed to apply"); - } - - @Shadow @Final private BlockableEventLoop mainThreadExecutor; @Shadow @Final ServerLevel level; @Shadow @Final private ChunkMap.DistanceManager distanceManager; - @AddFieldToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkMap.class), field = @FieldSig(type = @Ref(ChunkProgressListener.class), name = "progressListener")) + + @Shadow @Final private static CompletableFuture>> UNLOADED_CHUNK_LIST_FUTURE; + @Shadow @Final private static ChunkResult> UNLOADED_CHUNK_LIST_RESULT; + + @Shadow private static double euclideanDistanceSquared(ChunkPos chunkPos, Vec3 pos) { + throw new IllegalStateException(); + } + + // TODO this one being on GlobalSet is a bit jank + @AddFieldToSets(sets = GlobalSet.class, owner = @Ref(ChunkMap.class), field = @FieldSig(type = @Ref(ChunkProgressListener.class), name = "progressListener")) private CloProgressListener cc_progressListener; @AddFieldToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkMap.class), field = @FieldSig(type = @Ref(ChunkStatusUpdateListener.class), name = "chunkStatusListener")) private CloStatusUpdateListener cc_cloStatusListener; // TODO once we can target non-return locations in constructors, do this when the vanilla field is set @Inject(method = "", at = @At("RETURN")) - private void cc_onInit(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, StructureTemplateManager structureManager, - Executor dispatcher, BlockableEventLoop mainThreadExecutor, LightChunkGetter lightChunk, ChunkGenerator generator, ChunkProgressListener progressListener, - ChunkStatusUpdateListener chunkStatusListener, Supplier overworldDataStorage, int viewDistance, boolean sync, CallbackInfo ci) { + private void cc_onInit(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, StructureTemplateManager structureManager, Executor dispatcher, + BlockableEventLoop mainThreadExecutor, LightChunkGetter lightChunk, ChunkGenerator generator, ChunkProgressListener progressListener, + ChunkStatusUpdateListener chunkStatusListener, Supplier overworldDataStorage, TicketStorage ticketStorage, int serverViewDistance, boolean sync, CallbackInfo ci) { if (((CanBeCubic) level).cc_isCubic()) { cc_progressListener = ((CloProgressListener) progressListener); // TODO P2 (entities): actually pass in a cloStatusListener - since ChunkStatusUpdateListener is passed as a parameter, not sure what the best approach is without making our own constructor @@ -117,21 +130,25 @@ private void cc_onInit(ServerLevel level, LevelStorageSource.LevelStorageAccess } } + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("setChunkUnsaved(Lnet/minecraft/world/level/ChunkPos;)V")) + private native void cc_setCloUnsaved(CloPos cloPos); + /** * Returns the squared distance to the center of the cube. */ - @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("euclideanDistanceSquared(Lnet/minecraft/world/level/ChunkPos;" - + "Lnet/minecraft/world/entity/Entity;)D")) - private static double cc_euclideanDistanceSquared(CloPos cloPos, Entity entity) { + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("euclideanDistanceSquared(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/phys/Vec3;)D")) + private static double cc_euclideanDistanceSquared(CloPos cloPos, Vec3 vec3) { if (cloPos.isChunk()) { - throw new UnsupportedOperationException("Should not call euclideanDistanceSquared with a chunk position"); + // FIXME we shouldn't be getting euclidean distance for chunks, as this doesn't make sense in context + return euclideanDistanceSquared(cloPos.chunkPos(), vec3); +// throw new UnsupportedOperationException("Should not call euclideanDistanceSquared with a chunk position"); } double cubeCenterX = Coords.cubeToCenterBlock(cloPos.getX()); double cubeCenterY = Coords.cubeToCenterBlock(cloPos.getX()); double cubeCenterZ = Coords.cubeToCenterBlock(cloPos.getX()); - double dx = cubeCenterX - entity.getX(); - double dy = cubeCenterY - entity.getY(); - double dz = cubeCenterZ - entity.getZ(); + double dx = cubeCenterX - vec3.x(); + double dy = cubeCenterY - vec3.y(); + double dz = cubeCenterZ - vec3.z(); return dx * dx + dy * dy + dz * dz; } @@ -140,7 +157,7 @@ private static double cc_euclideanDistanceSquared(CloPos cloPos, Entity entity) // These methods are not copied due to taking 3 ints instead of 2 @Override public boolean cc_isChunkTracked(ServerPlayer player, int x, int y, int z) { - return ((CloTrackingView) player.getChunkTrackingView()).cc_contains(x, y, z) + return ((CCServerPlayer) player).cc_getCloTrackingView().cc_contains(x, y, z) // TODO this requires PlayerChunkSender to accept Clo longs && !player.connection.chunkSender.isPending(CloPos.cubeAsLong(x, y, z)); } @@ -162,14 +179,10 @@ private boolean cc_isChunkOnTrackedBorder(ServerPlayer player, int x, int y, int // TODO getChunkDebugData - low prio - // dasm + mixin + //region [cc_getChunkRangeFuture dasm + mixin] @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getChunkRangeFuture(Lnet/minecraft/server/level/ChunkHolder;ILjava/util/function/IntFunction;)Ljava/util/concurrent/CompletableFuture;")) - private native CompletableFuture, ChunkHolder.ChunkLoadingFailure>> cc_getChunkRangeFuture(ChunkHolder cloHolder, int radius, - IntFunction statusByRadius); - private static ChunkStatus cc_getChildStatus(ChunkStatus status) { - int index = status.getIndex() + 1; - return index >= cc_CHUNK_STATUSES.size() ? ChunkStatus.FULL : cc_CHUNK_STATUSES.get(index); - } + private native CompletableFuture>> cc_getChunkRangeFuture(ChunkHolder cloHolder, int radius, + IntFunction statusByRadius); // TODO this could be substantially improved probably hopefully /** @@ -177,12 +190,15 @@ private static ChunkStatus cc_getChildStatus(ChunkStatus status) { */ @Dynamic @Inject(method = "cc_dasm$cc_getChunkRangeFuture", at = @At("HEAD"), cancellable = true) private void cc_onGetChunkRangeFuture(ChunkHolder cloHolder, int radius, IntFunction statusByRadius, - CallbackInfoReturnable, ChunkHolder.ChunkLoadingFailure>>> cir) { - CloPos pos = ((CloHolder) cloHolder).cc_getPos(); + CallbackInfoReturnable>>> cir) { + // Note that statusByRadius sometimes isn't actually correct for cubes beyond the first few steps, but getChunkRangeFuture is only called with parameters for which it's correct within the radius + CloPos pos = ((CloHolder) cloHolder).cc_getCloPos(); if (!pos.isCube()) return; // The vanilla method has an early exit for radius=0 here; this is not valid for cubes because even if radius=0 we still depend on chunks that neighbor the cube - List cloHolders = new ArrayList<>(); - List expectedStatuses = new ArrayList<>(); + int cubeDiameter = radius * 2 + 1; + int chunkDiameter = cubeDiameter * CubicConstants.DIAMETER_IN_SECTIONS; + int futureCount = cubeDiameter * cubeDiameter * cubeDiameter + chunkDiameter * chunkDiameter; + List>> futures = new ArrayList<>(futureCount); int middleCubeIndex = -1; for (int dz = -radius; dz <= radius; dz++) { for (int dx = -radius; dx <= radius; dx++) { @@ -193,109 +209,72 @@ private void cc_onGetChunkRangeFuture(ChunkHolder cloHolder, int radius, IntFunc for (int sectionX = 0; sectionX < CubicConstants.DIAMETER_IN_SECTIONS; sectionX++) { ChunkHolder holder = this.getUpdatingChunkIfPresent(CloPos.chunkAsLong(Coords.cubeToSection(pos.getX()+dx, sectionX), Coords.cubeToSection(pos.getZ()+dz, sectionZ))); if (holder == null) { - var pos1 = new ChunkPos(Coords.cubeToSection(pos.getX()+dx, sectionX), Coords.cubeToSection(pos.getZ()+dz, sectionZ)); - cir.setReturnValue(CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { - @Override - public String toString() { - return "Unloaded " + pos1; - } - }))); + cir.setReturnValue(UNLOADED_CHUNK_LIST_FUTURE); return; } ChunkStatus expectedStatus = statusByRadius.apply(chunkDistance); - // getChunkRangeFuture statusByRadius returns the status that is depended on, not the actual destination status. for non-central chunks that's fine, - // but for the chunks intersecting the center cube, the central cube reaching the destination status depends on the intersecting chunks reaching the destination status, not its parent. - if (chunkDistance == 0) expectedStatus = cc_getChildStatus(expectedStatus); - cloHolders.add(holder); - expectedStatuses.add(expectedStatus); + futures.add((CompletableFuture>) (Object) holder.scheduleChunkGenerationTask(expectedStatus, (ChunkMap) (Object) this)); } } for (int dy = -radius; dy <= radius; dy++) { if (dx == 0 && dy == 0 && dz == 0) { - middleCubeIndex = cloHolders.size(); + middleCubeIndex = futures.size(); } ChunkHolder holder = this.getUpdatingChunkIfPresent(CloPos.cubeAsLong(pos.getX()+dx, pos.getY()+dy, pos.getZ()+dz)); if (holder == null) { - var pos1 = CloPos.cube(pos.getX()+dx, pos.getY()+dy, pos.getZ()+dz); - cir.setReturnValue(CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { - @Override - public String toString() { - return "Unloaded " + pos1; - } - }))); + cir.setReturnValue(UNLOADED_CHUNK_LIST_FUTURE); return; } ChunkStatus expectedStatus = statusByRadius.apply(Math.max(chunkDistance, Math.abs(dy))); - cloHolders.add(holder); - expectedStatuses.add(expectedStatus); + futures.add((CompletableFuture>) (Object) holder.scheduleChunkGenerationTask(expectedStatus, (ChunkMap) (Object) this)); } } } - // Vanilla gets futures for each individual ChunkHolder and uses Util.sequence to combine them; - // we instead use CloCollectorFuture, and add a listener to each CloHolder that notifies the collector when that CloHolder has reached the desired stage. - // This saves several gigabytes of CompletableFuture objects. - var cloCollectorFuture = new CloCollectorFuture(cloHolders.size()); - // Lambda created outside the loop to avoid allocating it multiple times - BiConsumer, Throwable> cloCollectorCallback = (either, error) -> cloCollectorFuture.add(either, error, false); - for (int i = 0; i < cloHolders.size(); i++) { - var holder = cloHolders.get(i); - var expectedStatus = expectedStatuses.get(i); - if (i == middleCubeIndex) { - ((CloHolder) holder).cc_addCloStatusListener(expectedStatus, (either, error) -> cloCollectorFuture.add(either, error, true), (ChunkMap) (Object) this); - } else { - ((CloHolder) holder).cc_addCloStatusListener(expectedStatus, cloCollectorCallback, (ChunkMap) (Object) this); - } - } - - CompletableFuture, ChunkHolder.ChunkLoadingFailure>> combinedFuture = cloCollectorFuture.thenApply(p_183730_ -> { - List list2 = Lists.newArrayList(); - int k1 = 0; + // Vanilla expects that the center chunk is in the middle of the list; this is not the case for cubes, so we manually swap the center cube to the middle + // - this is a """temporary""" approach, that we may or may not actually fix later. + Collections.swap(futures, middleCubeIndex, futures.size() / 2); - for(final Either either : p_183730_) { - if (either == null) { - throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); - } + cir.setReturnValue(Util.sequence(futures).thenApply(resultList -> { + List outputList = new ArrayList<>(resultList.size()); - Optional optional = either.left(); - if (optional.isEmpty()) { - int index = k1; - return Either.right(new ChunkHolder.ChunkLoadingFailure() { - @Override - public String toString() { - // TODO we should actually show the position here, not just the index - see vanilla method - return "Unloaded " + index + " " + either.right().get(); - } - }); - } + for(final ChunkResult chunkResult : resultList) { + if (chunkResult == null) { + throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); + } - list2.add(optional.get()); - ++k1; + CloAccess cloAccess = chunkResult.orElse(null); + if (cloAccess == null) { + return UNLOADED_CHUNK_LIST_RESULT; } - return Either.left(list2); + outputList.add(cloAccess); } - ); - // TODO verify whether this addSaveDependency logic is correct for cubes, especially for radius=0 - for (ChunkHolder holder : cloHolders) { - ((CloHolder) holder).cc_addSaveDependency("getChunkRangeFuture " + pos + " " + radius, combinedFuture); - } - - cir.setReturnValue(combinedFuture); + return ChunkResult.of(outputList); + })); } + //endregion - // dasm + mixin - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updateChunkScheduling(JILnet/minecraft/server/level/ChunkHolder;I)Lnet/minecraft/server/level/ChunkHolder;")) - @Nullable public native ChunkHolder cc_updateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel); + //region [cc_updateCubeScheduling dasm + mixin] + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(useRedirectSets = ChunkToCubeSet.class, value = @MethodSig("updateChunkScheduling(JILnet/minecraft/server/level/ChunkHolder;I)Lnet/minecraft/server/level/ChunkHolder;")) + @Nullable public native ChunkHolder cc_updateCubeScheduling(long cubePos, int newLevel, @Nullable ChunkHolder holder, int oldLevel); - // TODO this is a bit jank; maybe things that call this method should be altered instead? @Inject(method = "updateChunkScheduling", at = @At("HEAD"), cancellable = true) - private void cc_onUpdateChunkScheduling(long chunkPos, int newLevel, ChunkHolder holder, int oldLevel, CallbackInfoReturnable cir) { - if (((CanBeCubic) level).cc_isCubic()) { - cir.setReturnValue(cc_updateChunkScheduling(chunkPos, newLevel, holder, oldLevel)); + private void cc_onUpdateChunkScheduling(long cloPos, int newLevel, ChunkHolder holder, int oldLevel, CallbackInfoReturnable cir) { + if (((CanBeCubic) level).cc_isCubic() && CloPos.isCube(cloPos)) { + cir.setReturnValue(cc_updateCubeScheduling(cloPos, newLevel, holder, oldLevel)); } } + //endregion + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) + private native void cc_onLevelChange(CloPos cloPos, IntSupplier intsupplier, int i, IntConsumer intconsumer); + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) + public void cc_onCubeLevelChange(CubePos cubePos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter) { + cc_onLevelChange(CloPos.cube(cubePos), queueLevelGetter, ticketLevel, queueLevelSetter); + } @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("saveAllChunks(Z)V")) public native void cc_saveAllChunks(boolean flush); @@ -308,105 +287,32 @@ private void cc_onSaveAllChunks(boolean flush, CallbackInfo ci) { } } - // P4: scheduleUnload lambda we'll want to mirror the forge API for cubes - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("scheduleUnload(JLnet/minecraft/server/level/ChunkHolder;)V")) - private native void cc_scheduleUnload(long chunkPos, ChunkHolder chunkHolder); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("saveChunksEagerly(Ljava/util/function/BooleanSupplier;)V")) + private native void cc_saveClosEagerly(BooleanSupplier hasMoreTime); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("schedule(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;")) - public native CompletableFuture> cc_schedule(ChunkHolder holder, ChunkStatus status); + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("scheduleUnload(JLnet/minecraft/server/level/ChunkHolder;)V")) + private void cc_scheduleUnload(long chunkPos, ChunkHolder chunkHolder) { + // TODO (P2) save/load + } - // dasm + mixin + //region [cc_scheduleChunkLoad dasm + mixin] @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("scheduleChunkLoad(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) - private native CompletableFuture> cc_scheduleChunkLoad(CloPos cloPos); - - /** - * Loading cubes at EMPTY requires additional logic to ensure that the corresponding chunks are loaded first - * (Unlike other statuses, this dependency is not handled in {@link ChunkMap#getChunkRangeFuture}) - */ - @Dynamic @Inject(method = "cc_dasm$cc_scheduleChunkLoad", at = @At("HEAD"), cancellable = true) - private void cc_onScheduleChunkLoad(CloPos pos, - CallbackInfoReturnable>> cir) { - if (!pos.isCube()) return; - // TODO this is a bit of a disaster, why did mojang ever design their code around futures of Eithers - // Logic for loading cube-adjacent chunks first, similar to getChunkRangeFuture - List>> chunkLoadFutures = new ArrayList<>(); - - for (int sectionZ = 0; sectionZ < CubicConstants.DIAMETER_IN_SECTIONS; sectionZ++) { - for (int sectionX = 0; sectionX < CubicConstants.DIAMETER_IN_SECTIONS; sectionX++) { - final CloPos chunkPos = CloPos.chunk(Coords.cubeToSection(pos.getX(), sectionX), Coords.cubeToSection(pos.getZ(), sectionZ)); - long chunkPosLong = chunkPos.toLong(); - ChunkHolder chunkholder = this.getUpdatingChunkIfPresent(chunkPosLong); - if (chunkholder == null) { // This shouldn't occur as DistanceManager should add chunks to the ChunkMap before their corresponding cubes - cir.setReturnValue(CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { - @Override - public String toString() { - return "Unloaded " + chunkPos; - } - }))); - return; - } - - CompletableFuture> completablefuture = ((CloHolder) chunkholder).cc_getOrScheduleFuture( - ChunkStatus.EMPTY, (ChunkMap) (Object) this - ); - chunkLoadFutures.add(completablefuture); - } - } - - CompletableFuture>> chunkResultsFuture = Util.sequence(chunkLoadFutures); - CompletableFuture, ChunkHolder.ChunkLoadingFailure>> allChunksLoadedFuture = chunkResultsFuture.thenApply(p_183730_ -> { - List list2 = Lists.newArrayList(); - int index = 0; + private native CompletableFuture cc_scheduleChunkLoad(CloPos cloPos); - for(final Either either : p_183730_) { - if (either == null) { - throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); - } - - Optional optional = either.left(); - if (optional.isEmpty()) { - final int indexFinal = index; // thanks java - return Either.right(new ChunkHolder.ChunkLoadingFailure() { - @Override - public String toString() { - return "Unloaded chunk for cube " + pos + "offset: " + new ChunkPos(indexFinal % CubicConstants.DIAMETER_IN_SECTIONS, indexFinal / CubicConstants.DIAMETER_IN_SECTIONS) + " " + either.right().get(); - } - }); - } - - list2.add(optional.get()); - ++index; - } - - return Either.left(list2); - }); - // Wait for adjacent chunks to load, and then load the cube - // TODO allChunksLoadedFuture and cc_readChunk could (and probably should) run in parallel, it just makes this future logic a bit more complex - cir.setReturnValue(allChunksLoadedFuture - .thenCompose((result) -> result.map( - left -> this.cc_readChunk(pos).thenApply(p_214925_ -> p_214925_.filter(p_214928_ -> { - boolean flag = isChunkDataValid(p_214928_); - if (!flag) { - LOGGER.error("Chunk file at {} is missing level data, skipping", pos); - } - return flag; - })).>thenApplyAsync(p_313584_ -> { - this.level.getProfiler().incrementCounter("chunkLoad"); - // TODO (P2) loading save data -// if (p_313584_.isPresent()) { -// ChunkAccess chunkaccess = ChunkSerializer.read(this.level, this.poiManager, pos, p_313584_.get()); -// this.markPosition(pos, chunkaccess.getStatus().getChunkType()); -// return Either.left(chunkaccess); -// } else { - return Either.left(this.cc_createEmptyChunk(pos)); -// } - }, this.mainThreadExecutor).exceptionallyAsync(p_214888_ -> this.cc_handleChunkLoadFailure(p_214888_, pos), this.mainThreadExecutor), - right -> CompletableFuture.completedFuture(Either.right(right))))); + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("scheduleChunkLoad(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) + private CompletableFuture cc_scheduleChunkLoad(CubePos cubePos) { + return cc_scheduleChunkLoad(CloPos.cube(cubePos)); + } + @Dynamic @Redirect(method = "cc_dasm$cc_scheduleChunkLoad", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;prefetch(Lio/github/opencubicchunks/cc_core/world/level/CloPos;)Ljava/util/concurrent/CompletableFuture;")) + private CompletableFuture cc_onScheduleChunkLoad_poiManagerPreFetch(PoiManager instance, CloPos cloPos) { + // TODO (P2) save/load - PoiManager + return CompletableFuture.completedFuture(null); } + //endregion - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("handleChunkLoadFailure(Ljava/lang/Throwable;Lnet/minecraft/world/level/ChunkPos;)Lcom/mojang/datafixers/util/Either;")) - private native Either cc_handleChunkLoadFailure(Throwable exception, CloPos cloPos); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("handleChunkLoadFailure(Ljava/lang/Throwable;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/ChunkAccess;")) + private native ChunkResult cc_handleChunkLoadFailure(Throwable exception, CloPos cloPos); @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod( value = @MethodSig("createEmptyChunk(Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/ChunkAccess;")) @@ -415,35 +321,78 @@ public String toString() { @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("markPositionReplaceable(Lnet/minecraft/world/level/ChunkPos;)V")) private native void cc_markPositionReplaceable(CloPos cloPos); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("markPosition(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkStatus$ChunkType;)B")) - private native byte cc_markPosition(CloPos cloPos, ChunkStatus.ChunkType chunkType); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("markPosition(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/status/ChunkType;)B")) + private native byte cc_markPosition(CloPos cloPos, ChunkType chunkType); + + //region [cc_applyCubeStep dasm + mixin] + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(useRedirectSets = ChunkToCubeSet.class, value = @MethodSig("applyStep(Lnet/minecraft/server/level/GenerationChunkHolder;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;)Ljava/util/concurrent/CompletableFuture;")) + public native CompletableFuture cc_applyCubeStep( + GenerationChunkHolder generationchunkholder, CubeStep chunkstep, StaticCache3D cache + ); + + @Dynamic @Redirect(method = "cc_dasm$cc_applyCubeStep", at = @At(value = "INVOKE", target = "Lio/github/opencubicchunks/cubicchunks/util/StaticCache3D;get(II)Ljava/lang/Object;")) + private Object cc_onApplyCubeStep_staticCacheGet(StaticCache3D instance, int x, int z, @Local(ordinal = 0) CubePos cubePos) { + return instance.get(cubePos.getX(), cubePos.getY(), cubePos.getZ()); + } + //endregion - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("scheduleChunkGeneration(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;")) - private native CompletableFuture> cc_scheduleChunkGeneration(ChunkHolder chunkHolder, ChunkStatus chunkStatus); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("scheduleGenerationTask(Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/server/level/ChunkGenerationTask;")) + public native ChunkGenerationTask cc_scheduleGenerationTask(ChunkStatus chunkstatus, CloPos cloPos); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("releaseLightTicket(Lnet/minecraft/world/level/ChunkPos;)V")) - public native void cc_releaseLightTicket(CloPos cloPos); + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("scheduleGenerationTask(Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/server/level/ChunkGenerationTask;")) + public ChunkGenerationTask cc_scheduleGenerationTask(ChunkStatus chunkstatus, CubePos cubePos) { + return cc_scheduleGenerationTask(chunkstatus, CloPos.cube(cubePos)); + } - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getDependencyStatus(Lnet/minecraft/world/level/chunk/ChunkStatus;I)Lnet/minecraft/world/level/chunk/ChunkStatus;")) - private native ChunkStatus cc_getDependencyStatus(ChunkStatus chunkStatus, int p_140264_); + //region [cc_runGenerationTask dasm + mixin] + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("runGenerationTask(Lnet/minecraft/server/level/ChunkGenerationTask;)V")) + private native void cc_runGenerationTask(ChunkGenerationTask chunkgenerationtask); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("protoChunkToFullChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) - private native CompletableFuture> cc_protoChunkToFullChunk(ChunkHolder holder); + // Delegate to the cube method for cubes + @Inject(method = "runGenerationTask", at = @At("HEAD"), cancellable = true) + private void cc_onVanillaRunGenerationTask(ChunkGenerationTask task, CallbackInfo ci) { + if (((CloGenerationTask) task).cc_getCloPos().isCube()) { + ci.cancel(); + cc_runGenerationTask(task); + } + } + //endregion @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("prepareEntityTickingChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) - public native CompletableFuture> cc_prepareEntityTickingChunk(ChunkHolder holder); + public native CompletableFuture> cc_prepareEntityTickingChunk(ChunkHolder holder); + + @Inject(method = "prepareEntityTickingChunk", at = @At("HEAD"), cancellable = true) + private void cc_onVanillaPrepareEntityTickingChunk(ChunkHolder chunk, CallbackInfoReturnable>> cir) { + if (((CloHolder) chunk).cc_getCloPos().isCube()) { + cir.setReturnValue(cc_prepareEntityTickingChunk(chunk)); + } + } @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("prepareTickingChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) - public native CompletableFuture> cc_prepareTickingChunk(ChunkHolder holder); + public native CompletableFuture> cc_prepareTickingChunk(ChunkHolder holder); + + @Inject(method = "prepareTickingChunk", at = @At("HEAD"), cancellable = true) + private void cc_onVanillaPrepareTickingChunk(ChunkHolder chunk, CallbackInfoReturnable>> cir) { + if (((CloHolder) chunk).cc_getCloPos().isCube()) { + cir.setReturnValue(cc_prepareTickingChunk(chunk)); + } + } - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onChunkReadyToSend(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) - private native void cc_onChunkReadyToSend(LevelClo cloPos); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onChunkReadyToSend(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/LevelChunk;)V")) + private native void cc_onChunkReadyToSend(ChunkHolder chunkholder, LevelClo cloPos); @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("prepareAccessibleChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) - public native CompletableFuture> cc_prepareAccessibleChunk(ChunkHolder holder); + public native CompletableFuture> cc_prepareAccessibleChunk(ChunkHolder holder); + + @Inject(method = "prepareAccessibleChunk", at = @At("HEAD"), cancellable = true) + private void cc_onVanillaPrepareAccessibleChunk(ChunkHolder chunk, CallbackInfoReturnable>> cir) { + if (((CloHolder) chunk).cc_getCloPos().isCube()) { + cir.setReturnValue(cc_prepareAccessibleChunk(chunk)); + } + } - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("saveChunkIfNeeded(Lnet/minecraft/server/level/ChunkHolder;)Z")) - private native boolean cc_saveChunkIfNeeded(ChunkHolder holder); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("saveChunkIfNeeded(Lnet/minecraft/server/level/ChunkHolder;J)Z")) + private native boolean cc_saveChunkIfNeeded(ChunkHolder holder, long gameTime); // TODO (P2): for now we just don't save (requires more things to be CC-ified to not crash) @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("save(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z")) @@ -451,7 +400,7 @@ private boolean cc_save(CloAccess cloAccess) { return false; } -// // dasm + mixin +// //region [cc_save dasm + mixin] // @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("save(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z")) // private native boolean cc_save(CloAccess cloAccess); // @@ -463,6 +412,7 @@ private boolean cc_save(CloAccess cloAccess) { // LOGGER.error("Failed to save chunk or cube {}", cloAccess.cc_getCloPos().toString(), exception); // cir.setReturnValue(false); // } +// //endregion // This calls ChunkSerializer.getChunkTypeFromTag, which could be an issue? @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("isExistingChunkFull(Lnet/minecraft/world/level/ChunkPos;)Z")) @@ -489,9 +439,18 @@ private boolean cc_save(CloAccess cloAccess) { @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("readChunk(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) private native CompletableFuture> cc_readChunk(CloPos cloPos); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("collectSpawningChunks(Ljava/util/List;)V")) + native void cc_collectSpawningClos(List list); + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("forEachBlockTickingChunk(Ljava/util/function/Consumer;)V")) + native void cc_forEachBlockTickingClo(Consumer consumer); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("anyPlayerCloseEnoughForSpawning(Lnet/minecraft/world/level/ChunkPos;)Z")) public native boolean cc_anyPlayerCloseEnoughForSpawning(CloPos cloPos); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("anyPlayerCloseEnoughForSpawningInternal(Lnet/minecraft/world/level/ChunkPos;)Z")) + private native boolean cc_anyPlayerCloseEnoughForSpawningInternal(CloPos cloPos); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getPlayersCloseForSpawning(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/List;")) public native List cc_getPlayersCloseForSpawning(CloPos cloPos); @@ -504,7 +463,7 @@ private boolean cc_save(CloAccess cloAccess) { @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("move(Lnet/minecraft/server/level/ServerPlayer;)V")) public native void cc_move(ServerPlayer player); - // dasm + mixin + //region [cc_updateChunkTracking dasm + mixin] @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updateChunkTracking(Lnet/minecraft/server/level/ServerPlayer;)V")) private native void cc_updateChunkTracking(ServerPlayer player); @@ -512,17 +471,19 @@ private boolean cc_save(CloAccess cloAccess) { private int cc_onUpdateChunkTracking_getViewDistance(ChunkMap instance, ServerPlayer player, Operation original) { return Coords.sectionToCubeRenderDistance(original.call(instance, player)); } + //endregion - // dasm + mixin + //region [cc_applyChunkTrackingView dasm + mixin] @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("applyChunkTrackingView(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/server/level/ChunkTrackingView;)V")) private native void cc_applyChunkTrackingView(ServerPlayer player, CloTrackingView chunkTrackingView); @Dynamic @Redirect(method = "cc_dasm$cc_applyChunkTrackingView", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V")) private void cc_onApplyChunkTrackingView_setChunkCacheCenterPacket(ServerGamePacketListenerImpl instance, Packet packet, ServerPlayer player, CloTrackingView cloTrackingView) { - PacketDistributor.PLAYER.with(player).send(new CCClientboundSetCubeCacheCenterPacket(((CloTrackingView.Positioned) cloTrackingView).center().cubePos())); + PacketDistributor.sendToPlayer(player, new CCClientboundSetCubeCacheCenterPacket(((CloTrackingView.Positioned) cloTrackingView).center().cubePos())); } + //endregion - // dasm + mixin + //region [cc_getPlayers dasm + mixin] @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getPlayers(Lnet/minecraft/world/level/ChunkPos;Z)Ljava/util/List;")) public native List cc_getPlayers(CloPos pos, boolean boundaryOnly); @@ -535,6 +496,12 @@ private boolean cc_getPlayers_isChunkOnTrackedBorder(ChunkMap instance, ServerPl private boolean cc_getPlayers_isChunkTracked(ChunkMap instance, ServerPlayer player, int x, int z, @Local CloPos pos) { return this.cc_isChunkTracked(player, pos.getX(), pos.getY(), pos.getZ()); } + //endregion + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("getPlayers(Lnet/minecraft/world/level/ChunkPos;Z)Ljava/util/List;")) + @Override public List cc_getPlayers(CubePos pos, boolean boundaryOnly) { + return cc_getPlayers(CloPos.cube(pos), boundaryOnly); + } // Replace `SectionPos.chunk()` with `SectionPos.cc_cube()` unconditionally here @AddTransformToSets(GlobalSet.class) @TransformFromMethod(value = @MethodSig("tick(Ljava/util/function/BooleanSupplier;)V"), useRedirectSets = { ChunkToCloSet.class, SectionPosToCubeSet.class }) @@ -544,13 +511,18 @@ private boolean cc_getPlayers_isChunkTracked(ChunkMap instance, ServerPlayer pla public native void cc_tick(); @AddTransformToSets(GlobalSet.class) @TransformFromMethod(value = @MethodSig("processUnloads(Ljava/util/function/BooleanSupplier;)V")) - private native void processUnloads(BooleanSupplier hasMoreTime); + private native void cc_processUnloads(BooleanSupplier hasMoreTime); // TODO resendBiomesForChunks - only used for FillBiomeCommand @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onFullChunkStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/server/level/FullChunkStatus;)V")) public native void cc_onFullChunkStatusChange(CloPos cloPos, FullChunkStatus fullChunkStatus); + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("onFullChunkStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/server/level/FullChunkStatus;)V")) + public void cc_onFullChunkStatusChange(CubePos cubePos, FullChunkStatus fullChunkStatus) { + cc_onFullChunkStatusChange(CloPos.cube(cubePos), fullChunkStatus); + } + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("waitForLightBeforeSending(Lnet/minecraft/world/level/ChunkPos;I)V")) public native void cc_waitForLightBeforeSending(CloPos cloPos, int p_301130_); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskDispatcher.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskDispatcher.java new file mode 100644 index 00000000..1cec2ed0 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskDispatcher.java @@ -0,0 +1,28 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.server.level.CloTaskDispatcher; +import io.github.opencubicchunks.cubicchunks.server.level.CubeHolder; +import net.minecraft.server.level.ChunkTaskDispatcher; +import org.spongepowered.asm.mixin.Mixin; + +@Dasm(ChunkToCloSet.class) +@Mixin(ChunkTaskDispatcher.class) +public class MixinChunkTaskDispatcher implements CubeHolder.LevelChangeListener, CloTaskDispatcher { + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(ChunkTaskDispatcher.class), value = @MethodSig("onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) + @Override public native void cc_onLevelChange(CloPos cloPos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter); + + public void onLevelChange(CubePos cubePos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter) { + cc_onLevelChange(CloPos.cube(cubePos), queueLevelGetter, ticketLevel, queueLevelSetter); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueue.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueue.java index 6a7a66ba..f4b7cd9d 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueue.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueue.java @@ -8,7 +8,6 @@ import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; import net.minecraft.server.level.ChunkTaskPriorityQueue; import net.minecraft.world.level.ChunkPos; import org.spongepowered.asm.mixin.Mixin; @@ -35,6 +34,6 @@ private void cc_onResortChunkTasks(int queueLevel, ChunkPos chunkPos, int ticket } @UsedFromASM - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("resortChunkTasks(ILnet/minecraft/world/level/ChunkPos;I)V")) + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("resortChunkTasks(ILnet/minecraft/world/level/ChunkPos;I)V")) public native void cc_resortCubicTasks(int queueLevel, CloPos cloPos, int ticketLevel); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java deleted file mode 100644 index c56180dd..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; - -import static io.github.notstirred.dasm.api.annotations.transform.Visibility.PRIVATE; - -import java.util.function.Function; -import java.util.function.IntConsumer; -import java.util.function.IntSupplier; -import java.util.stream.Stream; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import io.github.notstirred.dasm.api.annotations.Dasm; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; -import io.github.notstirred.dasm.api.annotations.selector.MethodSig; -import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; -import io.github.opencubicchunks.cc_core.annotation.Public; -import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.server.level.CloHolder; -import io.github.opencubicchunks.cubicchunks.server.level.CloTaskPriorityQueueSorter; -import net.minecraft.server.level.ChunkHolder; -import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; -import net.minecraft.util.Unit; -import net.minecraft.util.thread.ProcessorHandle; -import net.minecraft.world.level.ChunkPos; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Dasm(ChunkToCloSet.class) -@Mixin(ChunkTaskPriorityQueueSorter.class) -public abstract class MixinChunkTaskPriorityQueueSorter implements CloTaskPriorityQueueSorter, CloHolder.LevelChangeListener, MarkableAsCubic { - protected boolean cc_isCubic; - - @Override public void cc_setCubic() { - cc_isCubic = true; - } - - @Override public boolean cc_isCubic() { - return cc_isCubic; - } - - // This is private because mixin requires static methods to be private, @Public sets it after mixin applies - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(value = @MethodSig("message(Lnet/minecraft/server/level/ChunkHolder;Ljava/lang/Runnable;)Lnet/minecraft/server/level/ChunkTaskPriorityQueueSorter$Message;"), visibility = PRIVATE) - @Public private static native ChunkTaskPriorityQueueSorter.Message cc_message(ChunkHolder chunkHolder, Runnable task); - - // This is private because mixin requires static methods to be private, @Public sets it after mixin applies - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(value = @MethodSig("message(Lnet/minecraft/server/level/ChunkHolder;Ljava/util/function/Function;)Lnet/minecraft/server/level/ChunkTaskPriorityQueueSorter$Message;"), visibility = PRIVATE) - @Public private static native ChunkTaskPriorityQueueSorter.Message cc_message(ChunkHolder chunkHolder, Function, T> task); - - /** - * This is a method that is only used for debugging, so we don't currently test it. - */ - @WrapOperation(method = "getDebugStatus", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;map(Ljava/util/function/Function;)Ljava/util/stream/Stream;")) - private Stream cc_replaceChunkPosInDebugStatus(Stream instance, Function function, Operation> original) { - if (!cc_isCubic) return original.call(instance, function); - return instance.map(CloPos::fromLong).map(CloPos::toString); - } - - @Inject(method = "onLevelChange", at = @At("HEAD")) - private void cc_onChunkOnLevelChange(ChunkPos chunkPos, IntSupplier p_140617_, int p_140618_, IntConsumer p_140619_, CallbackInfo ci) { - assert !cc_isCubic; - } - - @Override - @UsedFromASM - @TransformFromMethod(@MethodSig("onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) - public native void cc_onLevelChange(CloPos cloPos, IntSupplier p_140617_, int p_140618_, IntConsumer p_140619_); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTracker.java index 701158c8..08095ad0 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTracker.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTracker.java @@ -1,15 +1,14 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; import com.llamalad7.mixinextras.sugar.Local; -import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import net.minecraft.server.level.ChunkTracker; -import net.minecraft.server.level.DistanceManager; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.lighting.DynamicGraphMinFixedPoint; import org.spongepowered.asm.mixin.Mixin; @@ -25,9 +24,6 @@ * {@link ChunkTracker} is a class that determines the edges and as well as the values to be propagated to the edges in {@link DynamicGraphMinFixedPoint}. * It marks all chunks in a 1 chunk radius around the center as edges. Edge chunks have 1 level higher than the center chunk. *

- * {@link DistanceManager.ChunkTicketTracker} would be the equivalent to {@link net.minecraft.server.level.TickingTracker}. Since that - * implementation does not use {@link ChunkPos} directly, we do not need to mixin it unlike {@link net.minecraft.server.level.TickingTracker}. - *

* This mixin replaces {@link ChunkPos} with {@link CloPos} and adds in logic to handle propagation for cubes. */ @Mixin(ChunkTracker.class) @@ -168,8 +164,13 @@ private void cc_onGetComputedLevel(long pos, long excludedSourcePos, int level, /** * This function adds in new cubes and sorts them into the cube column map. *

- * It should be called from each onSetLevel implementation. - * For CC, this should only be {@link MixinTickingTracker#cc_onSetLevel(long, int)} and {@link MixinChunkTracker#cc_onSetLevel(long, int)}. + * It should be called from each implementation of {@link DynamicGraphMinFixedPoint#setLevel(long, int)} on a ChunkTracker subclass. + * For CC, this is handled by: + *

    + *
  • {@link MixinFixedPlayerDistanceChunkTracker#cc_onSetLevel(long, int)}.
  • + *
  • {@link MixinLoadingChunkTracker#cc_onSetLevel(long, int)}.
  • + *
  • {@link MixinSimulationChunkTracker#cc_onSetLevel(long, int)}.
  • + *
*/ protected void cc_onSetLevel(long pos, int level) { if (cc_isCubic && CloPos.isCube(pos)) { diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinCubeLevel.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinCubeLevel.java new file mode 100644 index 00000000..87ea5b7f --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinCubeLevel.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import io.github.opencubicchunks.cubicchunks.server.level.CubeLevel; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CubeLevel.class) +public class MixinCubeLevel { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinDistanceManager.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinDistanceManager.java index fb35cfaf..ce6e5018 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinDistanceManager.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinDistanceManager.java @@ -1,191 +1,79 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; -import com.google.common.collect.ImmutableSet; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import io.github.notstirred.dasm.api.annotations.Dasm; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; -import io.github.notstirred.dasm.api.annotations.selector.MethodSig; -import io.github.notstirred.dasm.api.annotations.selector.Ref; -import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; -import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; -import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; -import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTickingTracker; +import io.github.opencubicchunks.cubicchunks.world.level.CubicTicketStorage; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ChunkMap; -import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; import net.minecraft.server.level.DistanceManager; -import net.minecraft.server.level.TicketType; -import net.minecraft.server.level.TickingTracker; +import net.minecraft.server.level.LoadingChunkTracker; +import net.minecraft.server.level.SimulationChunkTracker; +import net.minecraft.server.level.Ticket; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.TicketStorage; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; /** * {@link DistanceManager} contains the main ticket hashmap and stores all the chunks that are loaded. * A ticket inside {@link DistanceManager} means that something is either requested to be loaded or is already loaded and needs to stay loaded. * It informs {@link ChunkMap} of what chunks it needs to generate/load/unload to satisfy the tickets. *

- * This mixin mostly just replaces calls to ChunkPos with CloPos and TicketType with CubicTicketType. + * This mixin mostly just replaces calls to ChunkPos with CloPos. */ @Dasm(ChunkToCloSet.class) @Mixin(DistanceManager.class) -public abstract class MixinDistanceManager implements CubicDistanceManager, MarkableAsCubic { +public abstract class MixinDistanceManager implements MarkableAsCubic { protected boolean cc_isCubic; - @Shadow @Final private DistanceManager.ChunkTicketTracker ticketTracker; + + @Shadow @Final private LoadingChunkTracker loadingChunkTracker; + @Shadow @Final private SimulationChunkTracker simulationChunkTracker; @Shadow @Final private DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter; - @Shadow @Final private TickingTracker tickingTicketsTracker; @Shadow @Final private DistanceManager.PlayerTicketTracker playerTicketManager; - @Shadow @Final ChunkTaskPriorityQueueSorter ticketThrottler; @Override public void cc_setCubic() { cc_isCubic = true; - ((MarkableAsCubic) this.ticketTracker).cc_setCubic(); + ((MarkableAsCubic) this.loadingChunkTracker).cc_setCubic(); + ((MarkableAsCubic) this.simulationChunkTracker).cc_setCubic(); ((MarkableAsCubic) this.naturalSpawnChunkCounter).cc_setCubic(); - ((MarkableAsCubic) this.tickingTicketsTracker).cc_setCubic(); ((MarkableAsCubic) this.playerTicketManager).cc_setCubic(); - ((MarkableAsCubic) this.ticketThrottler).cc_setCubic(); } @Override public boolean cc_isCubic() { return cc_isCubic; } - // TODO working with chunks in CC contexts still calls these methods; is this something we want to avoid? -// @Inject(method = {"addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V", -// "removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V", -// "addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V", -// "addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V", -// "removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V", -// "removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V", -// "updateChunkForced"}, at = @At("HEAD")) -// private void cc_onUseChunkPos(CallbackInfo ci){ -// assert !cc_isCubic; -// } - - @Override - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_addTicket(TicketType type, CloPos pos, int level, T value); - - @Override - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_removeTicket(TicketType type, CloPos pos, int level, T value); - - @Override - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_addRegionTicket(TicketType type, CloPos pos, int distance, T value); - - @Override - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public abstract void cc_addRegionTicket(TicketType type, CloPos pos, int distance, T value, boolean forceTicks); - - @Override - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_removeRegionTicket(TicketType type, CloPos pos, int distance, T value); - - @Override - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public abstract void cc_removeRegionTicket(TicketType type, CloPos pos, int distance, T value, boolean forceTicks); - - @UsedFromASM - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)V")) - public abstract void cc_updateCubeForced(CloPos pos, boolean add); - - // CubePos equivalents that delegate to their corresponding CloPos method - // For ticket types that hold a CloPos, we additionally must convert the ticket value. - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public void cc_addTicket(TicketType type, CubePos pos, int level, T value) { - cc_addTicket(type, CloPos.cube(pos), level, value instanceof CubePos cube ? (T) CloPos.cube(cube) : value); - } - - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public void cc_removeTicket(TicketType type, CubePos pos, int level, T value) { - cc_removeTicket(type, CloPos.cube(pos), level, value instanceof CubePos cube ? (T) CloPos.cube(cube) : value); - } - - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public void cc_addRegionTicket(TicketType type, CubePos pos, int distance, T value) { - cc_addRegionTicket(type, CloPos.cube(pos), distance, value instanceof CubePos cube ? (T) CloPos.cube(cube) : value); - } - - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public void cc_addRegionTicket(TicketType type, CubePos pos, int distance, T value, boolean forceTicks) { - cc_addRegionTicket(type, CloPos.cube(pos), distance, value instanceof CubePos cube ? (T) CloPos.cube(cube) : value, forceTicks); - } - - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public void cc_removeRegionTicket(TicketType type, CubePos pos, int distance, T value) { - cc_removeRegionTicket(type, CloPos.cube(pos), distance, value instanceof CubePos cube ? (T) CloPos.cube(cube) : value); - } - - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public void cc_removeRegionTicket(TicketType type, CubePos pos, int distance, T value, boolean forceTicks) { - cc_removeRegionTicket(type, CloPos.cube(pos), distance, value instanceof CubePos cube ? (T) CloPos.cube(cube) : value, forceTicks); - } - - @UsedFromASM - @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(DistanceManager.class), method = @MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)V")) - public void cc_updateCubeForced(CubePos pos, boolean add) { - cc_updateCubeForced(CloPos.cube(pos), add); - } - - /** - * This function replaces a TicketType with a CubicTicketType. - */ - @WrapOperation(method = "updateChunkForced", at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/TicketType;FORCED:Lnet/minecraft/server/level/TicketType;")) - private TicketType cc_replaceTicketTypeOnUpdateChunkForced(Operation> original) { - if(!cc_isCubic) return original.call(); - return CubicTicketType.FORCED; - } - /** * This function replaces the addTicket call with a cubic version instead. * - * This requires replacing the TicketType with a CubicTicketType and the ChunkPos with a CloPos. + * This requires replacing the ChunkPos with a CloPos. */ - @WrapWithCondition(method = "addPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/TickingTracker;addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - private boolean cc_replaceTicketTypeOnAddPlayer(TickingTracker instance, TicketType type, ChunkPos chunkPos, int ticketLevel, T key, SectionPos sectionPos) { + @WrapWithCondition(method = "addPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/TicketStorage;addTicket(Lnet/minecraft/server/level/Ticket;Lnet/minecraft/world/level/ChunkPos;)V")) + private boolean cc_replaceTicketTypeOnAddPlayer(TicketStorage instance, Ticket ticket, ChunkPos chunkPos, SectionPos sectionPos) { if(!cc_isCubic) return true; CloPos cloPos = CloPos.section(sectionPos); - ((CubicTickingTracker)instance).cc_addTicket(CubicTicketType.PLAYER, cloPos, ticketLevel, cloPos); + ((CubicTicketStorage) instance).cc_addTicket(ticket, cloPos); return false; } /** * This function replaces the removeTicket call with a cubic version instead. * - * This requires replacing the TicketType with a CubicTicketType and the ChunkPos with a CloPos. + * This requires replacing ChunkPos with a CloPos. */ - @WrapWithCondition(method = "removePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/TickingTracker;removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - private boolean cc_replaceTicketTypeOnRemovePlayer(TickingTracker instance, TicketType type, ChunkPos chunkPos, int ticketLevel, T key, SectionPos sectionPos) { + @WrapWithCondition(method = "removePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/TicketStorage;removeTicket(Lnet/minecraft/server/level/Ticket;Lnet/minecraft/world/level/ChunkPos;)V")) + private boolean cc_replaceTicketTypeOnRemovePlayer(TicketStorage instance, Ticket ticket, ChunkPos chunkPos, SectionPos sectionPos) { if(!cc_isCubic) return true; CloPos cloPos = CloPos.section(sectionPos); - ((CubicTickingTracker)instance).cc_removeTicket(CubicTicketType.PLAYER, cloPos, ticketLevel, cloPos); + ((CubicTicketStorage) instance).cc_removeTicket(ticket, cloPos); return false; } @@ -207,20 +95,5 @@ private long cc_replaceTicketTypeOnRemovePlayer(ChunkPos chunkPos, Operation cc_addIrremovableTicketsToSet(Object e1, Object e2, Object e3) { - return ImmutableSet.of(e1, e2, e3, CubicTicketType.UNKNOWN, CubicTicketType.LIGHT); - } - - @Override - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("runAllUpdates(Lnet/minecraft/server/level/ChunkMap;)Z")) - public native boolean cc_runAllUpdates(ChunkMap chunkManager); - - // TODO: Make mixins for dumpTickets if you're feeling ambitious (I'm not, and it is debug code, so it's not a priority) - + // TODO how does hasPlayersNearby work? } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinFixedPlayerDistanceChunkTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinFixedPlayerDistanceChunkTracker.java index 1d288008..3f9cc97d 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinFixedPlayerDistanceChunkTracker.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinFixedPlayerDistanceChunkTracker.java @@ -12,6 +12,4 @@ public abstract class MixinFixedPlayerDistanceChunkTracker extends MixinChunkTra private void cc_onSetLevel(long sectionPos, int level, CallbackInfo ci) { super.cc_onSetLevel(sectionPos, level); } - - // TODO do we care about dumpChunks? } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinGenerationChunkHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinGenerationChunkHolder.java new file mode 100644 index 00000000..f2293880 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinGenerationChunkHolder.java @@ -0,0 +1,130 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import javax.annotation.Nullable; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.server.level.CubeLevel; +import io.github.opencubicchunks.cubicchunks.server.level.CubicChunkMap; +import io.github.opencubicchunks.cubicchunks.server.level.GenerationCloHolder; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; +import net.minecraft.server.level.ChunkGenerationTask; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkResult; +import net.minecraft.server.level.GeneratingChunkMap; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * The vanilla {@link GenerationChunkHolder} class wraps completable futures for different statuses (load levels) of a single chunk and handles logic for scheduling generation of that chunk. + * This mixin adds cubic chunks equivalents for methods where necessary, to allow GenerationChunkHolder to dynamically wrap either a chunk or a cube (i.e. a CLO). + */ +@Dasm(ChunkToCubeSet.class) +@Mixin(GenerationChunkHolder.class) +public abstract class MixinGenerationChunkHolder implements GenerationCloHolder { + @Shadow @Final protected ChunkPos pos; + @Shadow @Final private AtomicReferenceArray>> futures; + @Shadow @Final private static ChunkResult NOT_DONE_YET; + + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(GenerationChunkHolder.class), field = @FieldSig(name = "pos", type = @Ref(ChunkPos.class))) + protected CubePos cc_cubePos; + + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(GenerationChunkHolder.class), method = @MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) + @Override public CloPos cc_getCloPos() { + if (cc_cubePos != null) { + return CloPos.cube(cc_cubePos); + } + return CloPos.chunk(pos); + } + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("(Lnet/minecraft/world/level/ChunkPos;)V")) + public MixinGenerationChunkHolder() { + throw new DasmFailedToApply(); + } + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("applyStep(Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/server/level/GeneratingChunkMap;Lnet/minecraft/util/StaticCache2D;)Ljava/util/concurrent/CompletableFuture;")) + native CompletableFuture> cc_applyCubeStep(CubeStep step, GeneratingChunkMap chunkMap, StaticCache3D cache); + + @WrapOperation(method = "updateHighestAllowedStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkLevel;generationStatus(I)Lnet/minecraft/world/level/chunk/status/ChunkStatus;")) + @Nullable protected ChunkStatus cc_onUpdateHighestAllowedStatus_generationStatus(int level, Operation original) { + if (cc_cubePos != null) { + return CubeLevel.cubeGenerationStatus(level); + } + return original.call(level); + } + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("replaceProtoChunk(Lnet/minecraft/world/level/chunk/ImposterProtoChunk;)V")) + public native void cc_replaceProtoCube(ImposterProtoCube cube); + + @WrapOperation(method = "rescheduleChunkTask", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;scheduleGenerationTask(Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/server/level/ChunkGenerationTask;")) + private ChunkGenerationTask cc_onRescheduleChunkTask_scheduleGenerationTask(ChunkMap instance, ChunkStatus status, ChunkPos chunkPos, Operation original) { + if (cc_cubePos != null) { + return ((CubicChunkMap) instance).cc_scheduleGenerationTask(status, cc_cubePos); + } + return original.call(instance, status, chunkPos); + } + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("completeFuture(Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/chunk/ChunkAccess;)V")) + private native void cc_completeFuture(ChunkStatus targetStatus, CubeAccess cubeAccess); + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("getChunkIfPresentUnchecked(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public native CubeAccess cc_getCubeIfPresentUnchecked(ChunkStatus status); + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("getChunkIfPresent(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public native CubeAccess cc_getCubeIfPresent(ChunkStatus status); + + @Shadow @Nullable public abstract ChunkAccess getLatestChunk(); + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("getLatestChunk()Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public native CubeAccess cc_getLatestCube(); + + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(GenerationChunkHolder.class), method = @MethodSig("getLatestChunk()Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public CloAccess cc_getLatestClo() { + if (cc_cubePos != null) { + return cc_getLatestCube(); + } + return (CloAccess) getLatestChunk(); + } + + @SuppressWarnings("unchecked") @Inject(method = "getPersistedStatus", at = @At("HEAD"), cancellable = true) + public void cc_onGetPersistedStatus(CallbackInfoReturnable cir) { + if (cc_cubePos != null) { + CompletableFuture> completablefuture = (CompletableFuture>) (Object) this.futures.get(ChunkStatus.EMPTY.getIndex()); + CubeAccess cubeAccess = completablefuture == null ? null : completablefuture.getNow((ChunkResult) (Object) NOT_DONE_YET).orElse(null); + cir.setReturnValue(cubeAccess == null ? null : cubeAccess.getPersistedStatus()); + } + } + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(GenerationChunkHolder.class), value = @MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) + public native CubePos cc_getCubePos(); + + // TODO getLatestStatus - only used for vanilla debug code +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTicketTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinLoadingChunkTracker.java similarity index 64% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTicketTracker.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinLoadingChunkTracker.java index 797df030..eb7fa41e 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTicketTracker.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinLoadingChunkTracker.java @@ -1,16 +1,14 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; -import net.minecraft.server.level.DistanceManager; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(DistanceManager.ChunkTicketTracker.class) -public abstract class MixinChunkTicketTracker extends MixinChunkTracker { +@Mixin(targets = "net.minecraft.server.level.LoadingChunkTracker") +public abstract class MixinLoadingChunkTracker extends MixinChunkTracker { @Inject(method = "setLevel", at = @At("HEAD")) private void cc_onSetLevel(long sectionPos, int level, CallbackInfo ci) { super.cc_onSetLevel(sectionPos, level); } - // TODO if/when we replace ChunkHolder with a cubic equivalent we'll need mixins here } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerTicketTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerTicketTracker.java index 3a4bbe00..a0c18efc 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerTicketTracker.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerTicketTracker.java @@ -4,16 +4,11 @@ import java.util.function.IntSupplier; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.mixin.access.common.DistanceManagerAccess; -import io.github.opencubicchunks.cubicchunks.server.level.CloTaskPriorityQueueSorter; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; -import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; +import io.github.opencubicchunks.cubicchunks.server.level.CloTaskDispatcher; import net.minecraft.server.level.DistanceManager; -import net.minecraft.server.level.Ticket; -import net.minecraft.server.level.TicketType; +import net.minecraft.server.level.ThrottlingChunkTaskDispatcher; import net.minecraft.world.level.ChunkPos; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -24,26 +19,14 @@ public abstract class MixinPlayerTicketTracker extends MixinFixedPlayerDistanceChunkTracker { @SuppressWarnings("target") @Shadow @Final DistanceManager this$0; - - /** - * This modifies the call to new Ticket to use a CloPos and CubicTicketType instead of a ChunkPos and TicketType. - */ - @WrapOperation(method = "onLevelChange(JIZZ)V", at = @At(value = "NEW", - target = "(Lnet/minecraft/server/level/TicketType;ILjava/lang/Object;)Lnet/minecraft/server/level/Ticket;")) - private Ticket cc_onTicketConstruct(TicketType type, int ticketLevel, Object key, Operation original) { - if (!cc_isCubic) - return original.call(type, ticketLevel, key); - return original.call(CubicTicketType.PLAYER, ticketLevel, CloPos.fromLong(((ChunkPos) key).toLong())); - } - /** - * This modifies the lambda inside Distance.this.ticketThrottler.onLevelChange to use a CloPos instead of a ChunkPos. + * This modifies the lambda inside Distance.this.ticketDispatcher.onLevelChange to use a CloPos instead of a ChunkPos. */ - @WrapWithCondition(method = "runAllUpdates", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkTaskPriorityQueueSorter;onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) - private boolean cc_onRunAllUpdates(ChunkTaskPriorityQueueSorter instance, ChunkPos chunkPos, IntSupplier p_140617_, int p_140618_, IntConsumer p_140619_) { + @WrapWithCondition(method = "runAllUpdates", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ThrottlingChunkTaskDispatcher;onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) + private boolean cc_onRunAllUpdates(ThrottlingChunkTaskDispatcher instance, ChunkPos chunkPos, IntSupplier intSupplier, int i, IntConsumer intConsumer) { if(!cc_isCubic) return true; - ((CloTaskPriorityQueueSorter)((DistanceManagerAccess)this$0).cc_ticketThrottler()) - .cc_onLevelChange(CloPos.fromLong(chunkPos.toLong()), p_140617_, p_140618_, p_140619_); + ((CloTaskDispatcher)((DistanceManagerAccess)this$0).cc_ticketDispatcher()) + .cc_onLevelChange(CloPos.fromLong(chunkPos.toLong()), intSupplier, i, intConsumer); return false; } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerChunkCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerChunkCache.java index 7157c49e..ed6424f5 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerChunkCache.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerChunkCache.java @@ -1,6 +1,7 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.BooleanSupplier; @@ -9,12 +10,7 @@ import javax.annotation.Nullable; -import com.google.common.collect.Lists; -import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.mojang.datafixers.DataFixer; -import com.mojang.datafixers.util.Either; import io.github.notstirred.dasm.api.annotations.Dasm; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; @@ -28,38 +24,35 @@ import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.mixin.access.common.ChunkMapAccess; import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.MixinChunkSource; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; -import io.github.opencubicchunks.cubicchunks.server.level.CloHolder; -import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; -import net.minecraft.Util; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.DistanceManager; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.util.profiling.ProfilerFiller; -import net.minecraft.world.level.GameRules; +import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.LocalMobCapCalculator; import net.minecraft.world.level.NaturalSpawner; -import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LightChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.ChunkStatusUpdateListener; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.storage.LevelStorageSource; @@ -92,6 +85,9 @@ public abstract class MixinServerChunkCache extends MixinChunkSource implements @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerChunkCache.class), field = @FieldSig(type = @Ref(CloAccess[].class), name = "lastChunk")) private final CubeAccess[] cc_lastCube = new CubeAccess[CACHE_SIZE]; + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerChunkCache.class), field = @FieldSig(type = @Ref(List.class), name = "spawningChunks")) + private final List cc_spawningCubes = new ObjectArrayList<>(); + @Shadow @Final public ServerLevel level; @Shadow @Nullable protected abstract ChunkHolder getVisibleChunkIfPresent(long pChunkPos); @@ -110,7 +106,9 @@ public abstract class MixinServerChunkCache extends MixinChunkSource implements @Shadow protected abstract void getFullChunk(long p_8371_, Consumer p_8372_); - @Inject(method = "", at = @At("io.github.opencubicchunks.cubicchunks.ConstructorSuper")) + @Shadow @Final private Set chunkHoldersToBroadcast; + + @Inject(method = "", at = @At("CTOR_HEAD")) private void cc_onInit(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, StructureTemplateManager structureManager, Executor dispatcher, ChunkGenerator generator, int viewDistance, int simulationDistance, boolean sync, ChunkProgressListener progressListener, ChunkStatusUpdateListener chunkStatusListener, Supplier overworldDataStorage, CallbackInfo ci) { @@ -119,10 +117,10 @@ private void cc_onInit(ServerLevel level, LevelStorageSource.LevelStorageAccess } } - @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(@MethodSig("storeInCache(JLnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/world/level/chunk/ChunkStatus;)V")) + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(@MethodSig("storeInCache(JLnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/world/level/chunk/status/ChunkStatus;)V")) private native void cc_storeInCache(long pChunkPos, CubeAccess pChunk, ChunkStatus pChunkStatus); - @TransformFromMethod(@MethodSig("getChunk(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @TransformFromMethod(@MethodSig("getChunk(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Lnet/minecraft/world/level/chunk/ChunkAccess;")) @Override @Nullable public native CubeAccess cc_getCube(int chunkX, @AddUnusedParam int chunkY, int chunkZ, ChunkStatus requiredStatus, boolean load); // mixin-into-dasm to replace call to getChunk with getCube @@ -138,7 +136,7 @@ private long cc_getCube_posAsLong(int pX, int pZ, int pXRepeated, int pY, int pZ } // The second through fifth params are the params to the call being redirected; the next three params are the x/y/z coordinates in the params of getCube - @Dynamic @Redirect(method = "cc_dasm$cc_getCube", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getChunkFutureMainThread(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;")) + @Dynamic @Redirect(method = "cc_dasm$cc_getCube", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getChunkFutureMainThread(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;")) private CompletableFuture cc_getCube_getChunkFutureMainThread(ServerChunkCache instance, int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, int chunkXRepeated, int chunkY, int chunkZRepeated) { return this.cc_getCubeFutureMainThread(chunkX, chunkY, chunkZ, requiredStatus, load); } @@ -167,11 +165,11 @@ private void cc_onClearCache(CallbackInfo ci) { // This method requires enough manual redirects that we just replace it entirely @Override - public CompletableFuture> cc_getCubeFuture( + public CompletableFuture> cc_getCubeFuture( int pX, int chunkY, int pZ, ChunkStatus pChunkStatus, boolean pLoad ) { boolean flag = Thread.currentThread() == this.mainThread; - CompletableFuture> completablefuture; + CompletableFuture> completablefuture; if (flag) { completablefuture = this.cc_getCubeFutureMainThread(pX, chunkY, pZ, pChunkStatus, pLoad); this.mainThreadProcessor.managedBlock(completablefuture::isDone); @@ -185,15 +183,8 @@ public CompletableFuture> cc return completablefuture; } - @WrapOperation(method = "getChunkFutureMainThread", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkHolder;getOrScheduleFuture(Lnet/minecraft/world/level/chunk/ChunkStatus;Lnet/minecraft/server/level/ChunkMap;)Ljava/util/concurrent/CompletableFuture;")) - private CompletableFuture> cc_onGetChunkFutureMainThread(ChunkHolder chunkHolder, ChunkStatus status, ChunkMap chunkMap, - Operation>> original) { - if (!cc_isCubic) return original.call(chunkHolder, status, chunkMap); - return (CompletableFuture) ((CloHolder) chunkHolder).cc_getOrScheduleFuture(status, chunkMap); - } - - @TransformFromMethod(@MethodSig("getChunkFutureMainThread(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;")) - private native CompletableFuture> cc_getCubeFutureMainThread( + @TransformFromMethod(@MethodSig("getChunkFutureMainThread(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;")) + private native CompletableFuture> cc_getCubeFutureMainThread( int pX, @AddUnusedParam int chunkY, int pZ, ChunkStatus pChunkStatus, boolean pLoad ); @@ -217,14 +208,6 @@ private CubePos cc_hasCube_posAsLong(int pX, int pZ, int pXRepeated, int pY, int throw new UnsupportedOperationException("not yet implemented"); } - @WrapOperation(method = "runDistanceManagerUpdates", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/DistanceManager;runAllUpdates(Lnet/minecraft/server/level/ChunkMap;)Z")) - private boolean cc_onRunDistanceManagerUpdates(DistanceManager instance, ChunkMap chunkMap, Operation original) { - if (!((CanBeCubic) level).cc_isCubic()) { - return original.call(instance, chunkMap); - } - return ((CubicDistanceManager) instance).cc_runAllUpdates(chunkMap); - } - @Inject(method = "tick", at = @At("HEAD"), cancellable = true) private void cc_onTick(BooleanSupplier hasTimeLeft, boolean tickChunks, CallbackInfo ci) { if (this.cc_isCubic) { @@ -236,78 +219,45 @@ private void cc_onTick(BooleanSupplier hasTimeLeft, boolean tickChunks, Callback @AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("tick(Ljava/util/function/BooleanSupplier;Z)V")) public native void cc_tick(BooleanSupplier hasTimeLeft, boolean tickChunks); - // This could maybe be DASM, but the mixins into the copied method would likely end up being quite complex - @AddMethodToSets(sets = GlobalSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("tickChunks()V")) - private void cc_tickChunks() { - long i = this.level.getGameTime(); - long j = i - this.lastInhabitedUpdate; - this.lastInhabitedUpdate = i; - if (!this.level.isDebug()) { - ProfilerFiller profilerfiller = this.level.getProfiler(); - profilerfiller.push("pollingChunks"); - profilerfiller.push("filteringLoadedChunks"); - List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); - - for(ChunkHolder chunkholder : ((ChunkMapAccess) this.chunkMap).cc_invokeGetChunks()) { - LevelClo levelchunk = ((CloHolder) chunkholder).cc_getTickingChunk(); - if (levelchunk != null) { - list.add(new CloAndHolder(levelchunk, chunkholder)); - } - } - - if (this.level.getServer().tickRateManager().runsNormally()) { - profilerfiller.popPush("naturalSpawnCount"); - int l = this.distanceManager.getNaturalSpawnChunkCount(); - NaturalSpawner.SpawnState naturalspawner$spawnstate = NaturalSpawner.createState( - l, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap) - ); - this.lastSpawnState = naturalspawner$spawnstate; - profilerfiller.popPush("spawnAndTick"); - boolean flag1 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING); - Util.shuffle(list, this.level.random); - int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); - boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L; - - for(CloAndHolder serverchunkcache$chunkandholder : list) { - LevelClo levelchunk1 = serverchunkcache$chunkandholder.chunk(); - CloPos chunkpos = levelchunk1.cc_getCloPos(); - // TODO isNaturalSpawningAllowed -// if ((this.level.isNaturalSpawningAllowed(chunkpos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkpos)) || this.distanceManager.shouldForceTicks(chunkpos.toLong())) { -// levelchunk1.incrementInhabitedTime(j); -// if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkpos)) { -// NaturalSpawner.spawnForChunk(this.level, levelchunk1, naturalspawner$spawnstate, this.spawnFriendlies, this.spawnEnemies, flag); -// } -// -// if (this.level.shouldTickBlocksAt(chunkpos.toLong())) { -// this.level.tickChunk(levelchunk1, k); -// } -// } - } - - profilerfiller.popPush("customSpawners"); - // TODO spawning -// if (flag1) { -// this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); -// } - } - - profilerfiller.popPush("broadcast"); - list.forEach(p_184022_ -> ((CloHolder) p_184022_.holder()).cc_broadcastChanges(p_184022_.chunk())); - profilerfiller.pop(); - profilerfiller.pop(); + @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("broadcastChangedChunks(Lnet/minecraft/util/profiling/ProfilerFiller;)V")) + private native void cc_broadcastChangedClos(ProfilerFiller profiler); + + @Inject(method = "broadcastChangedChunks", at = @At("HEAD"), cancellable = true) + private void cc_onVanillaBroadcastChangedChunks(ProfilerFiller profiler, CallbackInfo ci) { + if (cc_isCubic) { + ci.cancel(); + cc_broadcastChangedClos(profiler); } } - // needs manual impl because needs to use cube rather than chunk - @Override - @AddMethodToSets(sets = GlobalSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("blockChanged(Lnet/minecraft/core/BlockPos;)V")) - public void cc_blockChanged(BlockPos pos) { + @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("tickChunks(Lnet/minecraft/util/profiling/ProfilerFiller;J)V")) + private native void cc_tickClos(ProfilerFiller profiler, long timeInhabited); + + @Inject(method = "tickChunks(Lnet/minecraft/util/profiling/ProfilerFiller;J)V", at = @At("HEAD"), cancellable = true) + private void cc_onVanillaTickChunks(ProfilerFiller profiler, long timeInhabited, CallbackInfo ci) { + if (cc_isCubic) { + ci.cancel(); + cc_tickClos(profiler, timeInhabited); + } + } + + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("tickSpawningChunk(Lnet/minecraft/world/level/chunk/LevelChunk;JLjava/util/List;Lnet/minecraft/world/level/NaturalSpawner$SpawnState;)V")) + private void cc_tickSpawningClo(LevelClo levelClo, long timeInhabited, List spawnCategories, NaturalSpawner.SpawnState spawnState) { + // TODO (P2) + } + + @Inject(method = "blockChanged", at = @At("HEAD"), cancellable = true) + public void cc_onBlockChanged(BlockPos pos, CallbackInfo ci) { + if (!this.cc_isCubic) { + return; + } + ci.cancel(); int x = Coords.blockToCube(pos.getX()); int y = Coords.blockToCube(pos.getY()); int z = Coords.blockToCube(pos.getZ()); ChunkHolder chunkholder = this.getVisibleChunkIfPresent(CloPos.cubeAsLong(x, y, z)); - if (chunkholder != null) { - chunkholder.blockChanged(pos); + if (chunkholder != null && chunkholder.blockChanged(pos)) { + this.chunkHoldersToBroadcast.add(chunkholder); } } @@ -325,20 +275,40 @@ public void cc_onLightUpdate(LightLayer pType, SectionPos pPos) { // TODO (P2) lighting } - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public native void cc_addRegionTicket(TicketType pType, CloPos pPos, int pDistance, T pValue); - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public native void cc_addRegionTicket(TicketType p_8388_, CloPos p_8389_, int p_8390_, T p_8391_, boolean forceTicks); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("addTicket(Lnet/minecraft/server/level/Ticket;Lnet/minecraft/world/level/ChunkPos;)V")) + public native void cc_addTicket(Ticket ticket, CloPos cloPos); - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public native void cc_removeRegionTicket(TicketType pType, CloPos pPos, int pDistance, T pValue); - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public native void cc_removeRegionTicket(TicketType p_8439_, CloPos p_8440_, int p_8441_, T p_8442_, boolean forceTicks); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("addTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + public native void cc_addTicketWithRadius(TicketType ticket, CloPos cloPos, int radius); - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)V")) - public native void cc_updateChunkForced(CloPos pPos, boolean pAdd); + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("removeTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + public native void cc_removeTicketWithRadius(TicketType ticket, CloPos cloPos, int radius); + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(useRedirectSets = ChunkToCloSet.class, value = @MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)Z")) + public native boolean cc_updateCloForced(CloPos pPos, boolean pAdd); + + // Cube-specific methods that delegate to the corresponding Clo methods + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("addTicket(Lnet/minecraft/server/level/Ticket;Lnet/minecraft/world/level/ChunkPos;)V")) + public void cc_addTicket(Ticket ticket, CubePos cubePos) { + cc_addTicket(ticket, CloPos.cube(cubePos)); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("addTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + public void cc_addTicketWithRadius(TicketType ticket, CubePos cubePos, int radius) { + cc_addTicketWithRadius(ticket, CloPos.cube(cubePos), radius); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("removeTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + public void cc_removeTicketWithRadius(TicketType ticket, CubePos cubePos, int radius) { + cc_removeTicketWithRadius(ticket, CloPos.cube(cubePos), radius); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerChunkCache.class), method = @MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)Z\"")) + @Override public boolean cc_updateCubeForced(CubePos cubePos, boolean forced) { + return cc_updateCloForced(CloPos.cube(cubePos), forced); + } // TODO should probably be implemented properly, but is low priority (debug) - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("getChunkDebugData(Lnet/minecraft/world/level/ChunkPos;)Ljava/lang/String;")) + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getChunkDebugData(Lnet/minecraft/world/level/ChunkPos;)Ljava/lang/String;")) public native String cc_getChunkDebugData(CloPos pChunkPos); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerLevel.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerLevel.java index 354a325e..58b9a415 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerLevel.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerLevel.java @@ -4,21 +4,31 @@ import java.util.List; import java.util.concurrent.Executor; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.MixinLevel; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.server.level.CubicServerLevel; import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.ServerLevelData; @@ -34,7 +44,7 @@ public abstract class MixinServerLevel extends MixinLevel implements CubicServerLevel { @Shadow @Final private ServerChunkCache chunkSource; - @Inject(method = "", at = @At("io.github.opencubicchunks.cubicchunks.ConstructorSuper")) + @Inject(method = "", at = @At("CTOR_HEAD")) private void cc_onInit(MinecraftServer server, Executor dispatcher, LevelStorageSource.LevelStorageAccess levelStorageAccess, ServerLevelData serverLevelData, ResourceKey dimension, LevelStem levelStem, ChunkProgressListener progressListener, boolean isDebug, long biomeZoomSeed, List customSpawners, boolean tickTime, RandomSequences randomSequences, CallbackInfo ci) { @@ -48,12 +58,44 @@ private void cc_onInit(MinecraftServer server, Executor dispatcher, LevelStorage @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("startTickingChunk(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) public native void cc_startTickingClo(LevelClo chunk); + @WrapOperation(method = "setDefaultSpawnPos", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;removeTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + private void cc_onSetDefaultSpawnPos_removeTicketWithRadius(ServerChunkCache instance, TicketType ticket, ChunkPos chunkPos, int radius, Operation original, BlockPos pos) { + if (cc_isCubic) { + ((ServerCubeCache) instance).cc_removeTicketWithRadius(ticket, CloPos.cube(pos), radius); + } else { + original.call(instance, ticket, chunkPos, radius); + } + } + + @WrapOperation(method = "setDefaultSpawnPos", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;addTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + private void cc_onSetDefaultSpawnPos_addicketWithRadius(ServerChunkCache instance, TicketType ticket, ChunkPos chunkPos, int radius, Operation original, BlockPos pos) { + if (cc_isCubic) { + ((ServerCubeCache) instance).cc_addTicketWithRadius(ticket, CloPos.cube(pos), radius); + } else { + original.call(instance, ticket, chunkPos, radius); + } + } + + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(ServerLevel.class), method = @MethodSig("tickChunk(Lnet/minecraft/world/level/chunk/LevelChunk;I)V")) + public void cc_tickClo(LevelClo levelClo, int randomTickSpeed) { + if (levelClo instanceof LevelCube levelCube) { + cc_tickCube(levelCube, randomTickSpeed); + } { + // TODO (P2) chunk ticking for anything that still needs to happen on chunks (probably just the forge event for mods?) + } + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ServerLevel.class), method = @MethodSig("tickChunk(Lnet/minecraft/world/level/chunk/LevelChunk;I)V")) + public void cc_tickCube(LevelCube levelCube, int randomTickSpeed) { + // TODO (P2) cube ticking + } + + // TODO: comments below don't account for 1.20.4->1.21.5 changes; will need to check for other methods that need CC changes + // TODO: phase 3 - isNaturalSpawningAllowed // TODO: phase 3 - invalidateCapabilites, neoforge api - // TODO: phase 2 - tickCube - new function - // TODO: phase 4 - setCubeForced - new function // TODO: saveDebugReport - mixins, debug only, low priority, if we really really really really need it diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerPlayer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerPlayer.java index e5ce1a74..931d672e 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerPlayer.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinServerPlayer.java @@ -16,6 +16,7 @@ import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.entity.MixinEntity; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.server.level.CCServerPlayer; import io.github.opencubicchunks.cubicchunks.server.level.CloTrackingView; import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; import net.minecraft.core.BlockPos; @@ -29,7 +30,7 @@ @Dasm(ChunkToCloSet.class) @Mixin(ServerPlayer.class) -public abstract class MixinServerPlayer extends MixinEntity { +public abstract class MixinServerPlayer extends MixinEntity implements CCServerPlayer { @AddFieldToSets(sets = ChunkToCloSet.class, owner = @Ref(ServerPlayer.class), field = @FieldSig(type = @Ref(ChunkTrackingView.class), name = "chunkTrackingView")) private CloTrackingView cc_cloTrackingView = CloTrackingView.EMPTY; @@ -45,23 +46,6 @@ public CloPos cc_cubePositionAsClo() { return super.cc_cubePositionAsClo(); } - /** - * This mixin steals the x/y/z coordinates from a call to ChunkPos and replaces the ChunkPos in the addRegionTicketCall with a CloPos instead. - */ - @WrapWithCondition(method = "teleportTo(Lnet/minecraft/server/level/ServerLevel;DDDLjava/util/Set;FF)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level" - + "/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public boolean cc_wrapAddRegionTicket(ServerChunkCache instance, TicketType type, ChunkPos pos, int distance, T value, - @Local(ordinal = 0, argsOnly = true)double x, - @Local(ordinal = 1, argsOnly = true)double y, - @Local(ordinal = 2, argsOnly = true)double z) { - if (!((CanBeCubic) this.level()).cc_isCubic()) { - return true; - } - - ((ServerCubeCache)instance).cc_addRegionTicket(type, CloPos.cube(BlockPos.containing(x, y, z)), distance, value); - return false; - } - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getChunkTrackingView()Lnet/minecraft/server/level/ChunkTrackingView;")) public native CloTrackingView cc_getCloTrackingView(); @@ -71,4 +55,6 @@ public boolean cc_wrapAddRegionTicket(ServerChunkCache instance, TicketType< // TODO P3 :: findDimensionEntryPoint // TODO P3 :: changeDimension + + // FIXME (P2) teleportation code needs CC changes } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinSimulationChunkTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinSimulationChunkTracker.java new file mode 100644 index 00000000..dd1c17d5 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinSimulationChunkTracker.java @@ -0,0 +1,27 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.server.level.SimulationCloTracker; +import net.minecraft.server.level.SimulationChunkTracker; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Dasm(ChunkToCloSet.class) +@Mixin(SimulationChunkTracker.class) +public abstract class MixinSimulationChunkTracker extends MixinChunkTracker implements SimulationCloTracker { + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(SimulationChunkTracker.class), value = @MethodSig("getLevel(Lnet/minecraft/world/level/ChunkPos;)I")) + public native int cc_getLevel(CloPos cloPos); + + @Inject(method = "setLevel", at = @At("HEAD")) + private void cc_onSetLevel(long sectionPos, int level, CallbackInfo ci) { + super.cc_onSetLevel(sectionPos, level); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinTickingTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinTickingTracker.java deleted file mode 100644 index 020406ff..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinTickingTracker.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; - -import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import io.github.notstirred.dasm.api.annotations.Dasm; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; -import io.github.notstirred.dasm.api.annotations.selector.MethodSig; -import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTickingTracker; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.TicketType; -import net.minecraft.server.level.TickingTracker; -import net.minecraft.world.level.ChunkPos; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -/** - * {@link TickingTracker} is a class that manages a Long2ByteMap {@link TickingTracker#chunks} that contains the list of chunks that are ticking. - * This is for the purposes of separating simulation distance from render distance. - *

- * {@link TickingTracker#chunks} is indexed by ChunkPos, and the value is the level of the chunk. - * The meaning of the ChunkLevel is shown by {@link net.minecraft.server.level.ChunkLevel#byStatus(FullChunkStatus)}. - * The ByteMap only contains values that are below FULL status (level 33), since those are the only ones that need ticking. - *

- * {@link TickingTracker} also contains a Long2ObjectOpenHashMap {@link TickingTracker#tickets} which contains the actual list of tickets. - * The values from it get propagated to {@link TickingTracker#chunks} in {@link net.minecraft.server.level.ChunkTracker#computeLevelFromNeighbor}. - *

- * This mixin is used to convert {@link TickingTracker} to use {@link CloPos} instead of {@link ChunkPos}. - */ -@Dasm(ChunkToCloSet.class) -@Mixin(TickingTracker.class) -public abstract class MixinTickingTracker extends MixinChunkTracker implements CubicTickingTracker { - @Inject(method = "setLevel", at = @At("HEAD")) - private void cc_onSetLevel(long chunkPos, int level, CallbackInfo ci) { - super.cc_onSetLevel(chunkPos, level); - } - - @Inject(method = "getLevel(Lnet/minecraft/world/level/ChunkPos;)I", at = @At("HEAD")) - private void cc_onChunkGetLevel(ChunkPos chunkPos, CallbackInfoReturnable cir) { - assert !cc_isCubic; - } - - /** - * This mixin reverts the creation of a {@link ChunkPos}, then reads the {@link CloPos} from the raw long. - * We know if we are a Chunk or Cube based on {@link CloPos#CLO_Y_COLUMN_INDICATOR}, so there is no ambiguity here. - */ - @WrapWithCondition(method = "replacePlayerTicketsLevel", at = @At(value = "INVOKE", - target = "Lnet/minecraft/server/level/TickingTracker;addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - private boolean cc_onReplacePlayerTicketsLevel(TickingTracker instance, TicketType type, ChunkPos chunkPos, int ticketLevel, T key) { - if (!cc_isCubic) return true; - // if isCubic then we expect tickets to be TicketType not TicketType - var cloPos = CloPos.fromLong(chunkPos.toLong()); - this.cc_addTicket((TicketType) type, cloPos, ticketLevel, cloPos); - return false; - } - - /** - * We need to replace the reference to {@link TicketType#PLAYER} with {@link CubicTicketType#PLAYER} in {@link TickingTracker#replacePlayerTicketsLevel(int)}. - */ - @WrapOperation(method = "replacePlayerTicketsLevel", at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/TicketType;PLAYER:Lnet/minecraft/server/level/TicketType;")) - private TicketType cc_replaceTicketType(Operation> original) { - if(!cc_isCubic) return original.call(); - return CubicTicketType.PLAYER; - } - - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_addTicket(TicketType type, CloPos cloPos, int ticketLevel, T key); - - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_removeTicket(TicketType type, CloPos cloPos, int ticketLevel, T key); - - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("getLevel(Lnet/minecraft/world/level/ChunkPos;)I")) - public abstract int cc_getLevel(CloPos cloPos); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinLoggerChunkProgressListener.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinLoggerChunkProgressListener.java index 333940d1..9b8bf765 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinLoggerChunkProgressListener.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinLoggerChunkProgressListener.java @@ -8,7 +8,7 @@ import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; import io.github.opencubicchunks.cubicchunks.server.level.progress.CloProgressListener; import net.minecraft.server.level.progress.LoggerChunkProgressListener; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; @@ -18,6 +18,6 @@ public abstract class MixinLoggerChunkProgressListener implements CloProgressLis @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updateSpawnPos(Lnet/minecraft/world/level/ChunkPos;)V")) @Override public native void cc_updateSpawnPos(CloPos center); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkStatus;)V")) + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/status/ChunkStatus;)V")) @Override public native void cc_onStatusChange(CloPos chunkPosition, @Nullable ChunkStatus newStatus); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinProcessorChunkProgressListener.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinProcessorChunkProgressListener.java index 47d49bb5..ab2edbbf 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinProcessorChunkProgressListener.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinProcessorChunkProgressListener.java @@ -16,7 +16,7 @@ import io.github.opencubicchunks.cubicchunks.server.level.progress.CloProgressListener; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.server.level.progress.ProcessorChunkProgressListener; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -37,6 +37,6 @@ private void cc_onInit(ChunkProgressListener delegate, Executor dispatcher, Call @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updateSpawnPos(Lnet/minecraft/world/level/ChunkPos;)V")) @Override public native void cc_updateSpawnPos(CloPos center); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkStatus;)V")) + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/status/ChunkStatus;)V")) @Override public native void cc_onStatusChange(CloPos chunkPosition, @Nullable ChunkStatus newStatus); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinStoringChunkProgressListener.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinStoringChunkProgressListener.java index e391a68c..27797bde 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinStoringChunkProgressListener.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/progress/MixinStoringChunkProgressListener.java @@ -15,7 +15,7 @@ import io.github.opencubicchunks.cubicchunks.server.level.progress.CloProgressListener; import net.minecraft.server.level.progress.StoringChunkProgressListener; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import org.spongepowered.asm.mixin.Mixin; @Dasm(ChunkToCloSet.class) @@ -27,6 +27,6 @@ public abstract class MixinStoringChunkProgressListener implements CloProgressLi @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("updateSpawnPos(Lnet/minecraft/world/level/ChunkPos;)V")) @Override public native void cc_updateSpawnPos(CloPos center); - @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkStatus;)V")) + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/status/ChunkStatus;)V")) @Override public native void cc_onStatusChange(CloPos chunkPosition, @Nullable ChunkStatus newStatus); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/network/MixinPlayerChunkSender.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/network/MixinPlayerChunkSender.java index 8d878acd..f2b08f2d 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/network/MixinPlayerChunkSender.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/network/MixinPlayerChunkSender.java @@ -58,7 +58,7 @@ public class MixinPlayerChunkSender { @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(PlayerChunkSender.class), method = @MethodSig("dropChunk(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)V")) public void cc_dropClo(ServerPlayer player, CloPos cloPos) { if (!this.pendingChunks.remove(cloPos.toLong()) && player.isAlive()) { - PacketDistributor.PLAYER.with(player).send(new CCClientboundForgetLevelCloPacket(cloPos)); + PacketDistributor.sendToPlayer(player, new CCClientboundForgetLevelCloPacket(cloPos)); } } @@ -83,7 +83,7 @@ private void cc_onSendNextChunks(ServerPlayer player, CallbackInfo ci) { ++this.unacknowledgedBatches; // This packet can remain the same because it is just for timing purposes in order to determine how many chunks (or cubes) the client should request - servergamepacketlistenerimpl.send(new ClientboundChunkBatchStartPacket()); + servergamepacketlistenerimpl.send(ClientboundChunkBatchStartPacket.INSTANCE); // TODO P2 :: We need to send heightmap and lighting data, which would be contained in the Column @@ -111,7 +111,7 @@ private void cc_onSendNextChunks(ServerPlayer player, CallbackInfo ci) { @Unique private static void cc_sendCube(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelCube cube) { - PacketDistributor.PLAYER.with(packetListener.player).send(new CCClientboundLevelCubeWithLightPacket(cube)); + PacketDistributor.sendToPlayer(packetListener.player, new CCClientboundLevelCubeWithLightPacket(cube)); // ChunkPos chunkpos = chunk.getPos(); @@ -124,7 +124,7 @@ private static void cc_sendCube(ServerGamePacketListenerImpl packetListener, Ser @Unique private static void cc_sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) { - PacketDistributor.PLAYER.with(packetListener.player).send(new CCClientboundLevelChunkPacket(chunk.getPos())); + PacketDistributor.sendToPlayer(packetListener.player, new CCClientboundLevelChunkPacket(chunk.getPos())); // ChunkPos chunkpos = chunk.getPos(); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/entity/MixinEntity.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/entity/MixinEntity.java index fb015386..5fbdee43 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/entity/MixinEntity.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/entity/MixinEntity.java @@ -14,13 +14,9 @@ import io.github.opencubicchunks.cubicchunks.CanBeCubic; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; -import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; import io.github.opencubicchunks.cubicchunks.world.entity.EntityCubePosGetter; -import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; import io.github.opencubicchunks.cubicchunks.world.level.CubicLevelReader; import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.TicketType; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; @@ -67,19 +63,6 @@ private void cc_onSetPosRaw(double x, double y, double z, CallbackInfo ci) { } } - // In cubic levels, force-load the destination cube instead of chunk - @Inject(method = "teleportToWithTicket", at = @At("HEAD"), cancellable = true) - private void cc_onTeleportToWithTicket(double x, double y, double z, CallbackInfo ci) { - if (!((CanBeCubic) this.level).cc_isCubic()) return; - ci.cancel(); - if (this.level instanceof ServerLevel) { - CloPos cubePos = CloPos.cube(BlockPos.containing(x, y, z)); - ((ServerCubeCache) ((ServerLevel) this.level).getChunkSource()).cc_addRegionTicket(TicketType.POST_TELEPORT, cubePos, 0, this.getId()); - ((CubicLevel) this.level).cc_getCube(cubePos.getX(), cubePos.getY(), cubePos.getZ()); - this.teleportTo(x, y, z); - } - } - // In cubic levels, check for unloaded cubes instead of chunks @Inject(method = "touchingUnloadedChunk", at = @At("HEAD"), cancellable = true) private void cc_onTouchingUnloadedChunk(CallbackInfoReturnable cir) { @@ -93,4 +76,6 @@ private void cc_onTouchingUnloadedChunk(CallbackInfoReturnable cir) { int maxZ = Mth.ceil(aabb.maxZ); cir.setReturnValue(!((CubicLevelReader) this.level).cc_hasCubesAt(minX, minY, minZ, maxX, maxY, maxZ)); } + + // TODO (P2) teleportation code needs CC changes } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinBlockCollisions.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinBlockCollisions.java index 1f39dd83..191267e8 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinBlockCollisions.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinBlockCollisions.java @@ -11,11 +11,11 @@ import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cubicchunks.CanBeCubic; import io.github.opencubicchunks.cubicchunks.world.level.CubicCollisionGetter; -import net.minecraft.world.entity.Entity; import net.minecraft.world.level.BlockCollisions; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.CollisionGetter; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.CollisionContext; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -30,8 +30,8 @@ public class MixinBlockCollisions { @Shadow private long cachedBlockGetterPos; private boolean cc_isCubic; - @Inject(method = "", at = @At("io.github.opencubicchunks.cubicchunks.ConstructorSuper")) - private void cc_onInit(CollisionGetter collisionGetter, Entity entity, AABB box, boolean onlySuffocatingBlocks, BiFunction resultProvider, CallbackInfo ci) { + @Inject(method = "(Lnet/minecraft/world/level/CollisionGetter;Lnet/minecraft/world/phys/shapes/CollisionContext;Lnet/minecraft/world/phys/AABB;ZLjava/util/function/BiFunction;)V", at = @At("CTOR_HEAD")) + private void cc_onInit(CollisionGetter collisionGetter, CollisionContext context, AABB box, boolean onlySuffocatingBlocks, BiFunction resultProvider, CallbackInfo ci) { // TODO probably don't cast without an instanceof check in production - for dev it's fine since it will tell us we're missing something if (((CanBeCubic) collisionGetter).cc_isCubic()) cc_isCubic = true; } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevel.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevel.java index 4028260d..7c027271 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevel.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevel.java @@ -15,7 +15,6 @@ import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; -import io.github.opencubicchunks.cubicchunks.config.CommonConfig; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; @@ -33,12 +32,11 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkSource; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.material.FluidState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -82,7 +80,7 @@ public CubeAccess cc_getCube(int cubeX, int cubeY, int cubeZ, ChunkStatus status } } - @Inject(method = "", at = @At(value = "io.github.opencubicchunks.cubicchunks.ConstructorSuper")) + @Inject(method = "", at = @At(value = "CTOR_HEAD")) private void cc_init(CallbackInfo ci) { if(CubicChunks.config().shouldGenerateNewWorldsAsCC()) { this.cc_setCubic(); @@ -105,13 +103,13 @@ private LevelChunk cc_replaceLevelChunkInGetChunkAt(Level level, BlockPos blockP return original.call(level, blockPos); } - @WrapOperation(method = "setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;II)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;setBlockState(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Z)Lnet/minecraft/world/level/block/state/BlockState;")) - private BlockState cc_replaceLevelChunkInSetBlockState(LevelChunk levelChunk, BlockPos blockPos, BlockState blockState, boolean flag1, Operation original, + @WrapOperation(method = "setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;II)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;setBlockState(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Lnet/minecraft/world/level/block/state/BlockState;")) + private BlockState cc_replaceLevelChunkInSetBlockState(LevelChunk levelChunk, BlockPos blockPos, BlockState blockState, int flags, Operation original, @Share("levelCube") LocalRef levelCubeLocalRef) { if(cc_isCubic) { - return levelCubeLocalRef.get().setBlockState(blockPos, blockState, flag1); + return levelCubeLocalRef.get().setBlockState(blockPos, blockState, flags); } - return original.call(levelChunk, blockPos, blockState, flag1); + return original.call(levelChunk, blockPos, blockState, flags); } @WrapWithCondition(method = "setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;II)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;markAndNotifyBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;II)V")) @@ -209,8 +207,7 @@ private boolean cc_replaceHasChunkInIsLoaded(ChunkSource chunkSource, int x, int // loadedAndEntityCanStandOnFace // Uses an inject here since the entire second half of the method needs to be replaced anyways - @Inject(method = "loadedAndEntityCanStandOnFace", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getChunk(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)" - + "Lnet/minecraft/world/level/chunk/ChunkAccess;"), cancellable = true) + @Inject(method = "loadedAndEntityCanStandOnFace", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getChunk(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Lnet/minecraft/world/level/chunk/ChunkAccess;"), cancellable = true) private void cc_replaceGetChunkAtInLoadedAndEntityCanStandOnFace(BlockPos blockPos, Entity entity, Direction direction, CallbackInfoReturnable cir) { if(cc_isCubic) { CubeAccess cubeAccess = this.cc_getCube(Coords.blockToCube(blockPos.getX()), Coords.blockToCube(blockPos.getY()), Coords.blockToCube(blockPos.getZ()), ChunkStatus.FULL, false); @@ -224,7 +221,7 @@ private void cc_replaceGetChunkAtInLoadedAndEntityCanStandOnFace(BlockPos blockP private void cc_replaceBlockEntityChanged(BlockPos blockPos, CallbackInfo ci) { if(cc_isCubic) { if (this.cc_hasCubeAt(blockPos)) { - this.cc_getCubeAt(blockPos).setUnsaved(true); + this.cc_getCubeAt(blockPos).markUnsaved(); } ci.cancel(); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevelHeightAccessor.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevelHeightAccessor.java new file mode 100644 index 00000000..506bd2fa --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinLevelHeightAccessor.java @@ -0,0 +1,19 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level; + +import net.minecraft.world.level.LevelHeightAccessor; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +// FIXME temporary hack since cc_core still expects old method names +@Mixin(LevelHeightAccessor.class) +public interface MixinLevelHeightAccessor { + @Shadow int getMinY(); + @Shadow int getMaxY(); + default int getMinBuildHeight() { + return getMinY(); + } + + default int getMaxBuildHeight() { + return getMaxY(); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinTicketStorage.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinTicketStorage.java new file mode 100644 index 00000000..db378ac2 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/MixinTicketStorage.java @@ -0,0 +1,40 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level; + + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.world.level.CubicTicketStorage; +import net.minecraft.server.level.Ticket; +import net.minecraft.server.level.TicketType; +import net.minecraft.world.level.TicketStorage; +import org.spongepowered.asm.mixin.Mixin; + +@Dasm(ChunkToCloSet.class) +@Mixin(TicketStorage.class) +public class MixinTicketStorage implements CubicTicketStorage { + // TODO (P2) codec nonsense for save/load + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(TicketStorage.class), value = @MethodSig("addTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + public native void cc_addTicketWithRadius(TicketType ticketType, CloPos cloPos, int radius); + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(TicketStorage.class), value = @MethodSig("addTicket(Lnet/minecraft/server/level/Ticket;Lnet/minecraft/world/level/ChunkPos;)V")) + public native void cc_addTicket(Ticket ticket, CloPos cloPos); + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(TicketStorage.class), value = @MethodSig("removeTicketWithRadius(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;I)V")) + public native void cc_removeTicketWithRadius(TicketType ticketType, CloPos cloPos, int radius); + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(TicketStorage.class), value = @MethodSig("removeTicket(Lnet/minecraft/server/level/Ticket;Lnet/minecraft/world/level/ChunkPos;)V")) + public native void cc_removeTicket(Ticket ticket, CloPos cloPos); + + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(TicketStorage.class), value = @MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)Z")) + public native boolean cc_updateChunkForced(CloPos cloPos, boolean add); + + // TODO move to neoforge-specific mixin + @AddTransformToSets(ChunkToCloSet.class) @TransformFromMethod(owner = @Ref(TicketStorage.class), value = @MethodSig("shouldForceNaturalSpawning(Lnet/minecraft/world/level/ChunkPos;)Z")) + public native boolean cc_shouldForceNaturalSpawning(CloPos cloPos); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkSource.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkSource.java index 870b0991..53b3e340 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkSource.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkSource.java @@ -10,7 +10,7 @@ import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.world.level.chunk.ChunkSource; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import org.spongepowered.asm.mixin.Mixin; /** @@ -47,5 +47,7 @@ public boolean cc_hasCube(int x, int y, int z) { public abstract int cc_getLoadedCubeCount(); - public void cc_updateCubeForced(CubePos cubePos, boolean forced) {} + public boolean cc_updateCubeForced(CubePos cubePos, boolean forced) { + return false; + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java deleted file mode 100644 index dfb10cd6..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java +++ /dev/null @@ -1,175 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk; - -import static io.github.opencubicchunks.cc_core.utils.Utils.unsafeCast; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.function.Function; - -import com.google.common.collect.ImmutableList; -import com.mojang.datafixers.util.Either; -import io.github.notstirred.dasm.api.annotations.Dasm; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; -import io.github.notstirred.dasm.api.annotations.selector.FieldSig; -import io.github.notstirred.dasm.api.annotations.selector.MethodSig; -import io.github.notstirred.dasm.api.annotations.selector.Ref; -import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; -import io.github.notstirred.dasm.api.annotations.transform.Visibility; -import io.github.opencubicchunks.cc_core.annotation.Public; -import io.github.opencubicchunks.cc_core.api.CubicConstants; -import io.github.opencubicchunks.cc_core.utils.Coords; -import io.github.opencubicchunks.cubicchunks.CubicChunks; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; -import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; -import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import net.minecraft.Util; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ChunkHolder; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ThreadedLevelLightEngine; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.chunk.ChunkStatus; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@Dasm(GlobalSet.class) -@Mixin(ChunkStatus.class) -public class MixinChunkStatus { - @Shadow @Final private int range; - - @Shadow @Final private static List STATUS_BY_RANGE; - - private static final List STATUS_BY_RANGE_32 = ImmutableList.of( - ChunkStatus.FULL, - ChunkStatus.INITIALIZE_LIGHT, - ChunkStatus.CARVERS, - ChunkStatus.BIOMES, - ChunkStatus.STRUCTURE_STARTS, - ChunkStatus.STRUCTURE_STARTS, - ChunkStatus.STRUCTURE_STARTS, - ChunkStatus.STRUCTURE_STARTS - ); - - private static final List STATUS_BY_RANGE_64 = ImmutableList.of( - ChunkStatus.FULL, - ChunkStatus.INITIALIZE_LIGHT, - ChunkStatus.CARVERS, - ChunkStatus.BIOMES, - ChunkStatus.STRUCTURE_STARTS, - ChunkStatus.STRUCTURE_STARTS - ); - - private static final List STATUS_BY_RANGE_128 = ImmutableList.of( - ChunkStatus.FULL, - ChunkStatus.INITIALIZE_LIGHT, - ChunkStatus.CARVERS, - ChunkStatus.BIOMES, - ChunkStatus.STRUCTURE_STARTS - ); - - @AddFieldToSets(sets = { GlobalSet.class }, owner = @Ref(ChunkStatus.class), field = @FieldSig(type = @Ref(List.class), name = "STATUS_BY_RANGE")) - private static final List CUBE_STATUS_BY_RANGE = getStatusByRange(); - - @AddFieldToSets(sets = { GlobalSet.class }, owner = @Ref(ChunkStatus.class), field = @FieldSig(type = @Ref(IntList.class), name = "RANGE_BY_STATUS")) - private static final IntList CUBE_RANGE_BY_STATUS = Util.make(new IntArrayList(ChunkStatus.getStatusList().size()), (rangeByStatus) -> { - int range = 0; - - for (int status = ChunkStatus.getStatusList().size() - 1; status >= 0; --status) { - while (range + 1 < STATUS_BY_RANGE.size() && status <= STATUS_BY_RANGE.get(range + 1).getIndex()) { - ++range; - } - rangeByStatus.add(0, range); - } - }); - - private static List getStatusByRange() { - int cubeDiameter = CubicConstants.DIAMETER_IN_SECTIONS; - switch (cubeDiameter) { - case 1: - return STATUS_BY_RANGE; // same as vanilla - case 2: - return STATUS_BY_RANGE_32; - case 4: - return STATUS_BY_RANGE_64; - case 8: - return STATUS_BY_RANGE_128; - default: - throw new UnsupportedOperationException("Unsupported cube size " + cubeDiameter); - } - } - - // Temporary basic sinusoidal terrain, so we can generate a simple test world - private static CompletableFuture> generateBasicTerrain(CloAccess cloAccess) { - int amplitude = 20; - if (cloAccess instanceof CubeAccess cube) { - var blockPos = new BlockPos.MutableBlockPos(); - var blockState = Blocks.SMOOTH_STONE.defaultBlockState(); - int minY = cube.cc_getCubePos().minCubeY(); - int maxY = Math.min(cube.cc_getCubePos().maxCubeY(), CubicChunks.SUPERFLAT_HEIGHT + amplitude); - int cubeX = cube.cc_getCubePos().minCubeX(); - int cubeZ = cube.cc_getCubePos().minCubeZ(); - for (int y = minY; y <= maxY; y++) { - for (int x = 0; x < CubicConstants.DIAMETER_IN_BLOCKS; x++) { - for (int z = 0; z < CubicConstants.DIAMETER_IN_BLOCKS; z++) { - if (y + Math.round((amplitude*(Math.sin((x+cubeX)/8.0+(z+cubeZ)/21.0)+Math.cos((z+cubeZ)/13.0)))/2.0) <= CubicChunks.SUPERFLAT_HEIGHT) - cube.setBlockState(blockPos.set(x, y, z), blockState, false); - } - } - } - } - return CompletableFuture.completedFuture(Either.left(cloAccess)); - } - - // TODO (P2) proper generation logic; this currently ignores everything and only handles promotion from ProtoClo to LevelClo - @AddMethodToSets(sets = { ChunkToCloSet.class }, owner = @Ref(ChunkStatus.class), method = @MethodSig("generate(Ljava/util/concurrent/Executor;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager;Lnet/minecraft/server/level/ThreadedLevelLightEngine;Ljava/util/function/Function;Ljava/util/List;)Ljava/util/concurrent/CompletableFuture;")) - public CompletableFuture> cc_generate( - Executor exectutor, - ServerLevel level, - ChunkGenerator chunkGenerator, - StructureTemplateManager structureTemplateManager, - ThreadedLevelLightEngine lightEngine, - Function>> task, - List cache - ) { - CloAccess chunkaccess = cache.get(cache.size() / 2); - return ((Object) this == ChunkStatus.FULL ? task.apply(chunkaccess) : ((Object) this == ChunkStatus.NOISE) ? generateBasicTerrain(chunkaccess) : CompletableFuture.completedFuture(Either.left(chunkaccess))) - .thenApply( - p_281217_ -> { - p_281217_.ifLeft(p_290029_ -> { - if (p_290029_ instanceof ProtoClo protochunk && !protochunk.getStatus().isOrAfter((ChunkStatus) (Object) this)) { - protochunk.setStatus((ChunkStatus) (Object) this); - } - }); - - return unsafeCast(p_281217_); - } - ); - } - - // TODO should things in here actually be in GlobalSet? not sure if Chunks should also have lowered range in cubic worlds - // if we make it ChunkToCubeSet then it makes things slightly awkward since MixinChunkMap uses ChunkToCloSet normally - @AddMethodToSets(sets = { GlobalSet.class }, owner = @Ref(ChunkStatus.class), method = @MethodSig("getRange()I")) - public int cc_getRange() { - // TODO does this actually give the correct value? This is what was used in CC2 - return Coords.sectionToCubeCeil(this.range); - } - - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(visibility = Visibility.PRIVATE, value = @MethodSig("getStatusAroundFullChunk(I)Lnet/minecraft/world/level/chunk/ChunkStatus;")) - @Public private static native ChunkStatus cc_getStatusAroundFullCube(int radius); - - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(visibility = Visibility.PRIVATE, value = @MethodSig("maxDistance()I")) - @Public private static native int cc_maxDistance(); - - @AddTransformToSets(GlobalSet.class) @TransformFromMethod(visibility = Visibility.PRIVATE, value = @MethodSig("getDistance(Lnet/minecraft/world/level/chunk/ChunkStatus;)I")) - @Public private static native int cc_getDistance(ChunkStatus status); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/MixinCCChunkStatusTasks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/MixinCCChunkStatusTasks.java new file mode 100644 index 00000000..6b7e3f71 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/MixinCCChunkStatusTasks.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.status; + +import io.github.opencubicchunks.cubicchunks.world.level.chunk.status.CCChunkStatusTasks; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CCChunkStatusTasks.class) +public class MixinCCChunkStatusTasks { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/MixinChunkStatusTasks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/MixinChunkStatusTasks.java new file mode 100644 index 00000000..116ed32a --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/MixinChunkStatusTasks.java @@ -0,0 +1,130 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.status; + +import java.util.concurrent.CompletableFuture; + +import io.github.opencubicchunks.cubicchunks.CanBeCubic; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.status.CCChunkStatusTasks; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.util.StaticCache2D; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatusTasks; +import net.minecraft.world.level.chunk.status.ChunkStep; +import net.minecraft.world.level.chunk.status.WorldGenContext; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * When in a cubic level, we redirect to the corresponding methods on {@link CCChunkStatusTasks}. + */ +@Mixin(ChunkStatusTasks.class) +public class MixinChunkStatusTasks { + @Inject(method = "generateStructureStarts", at = @At("HEAD"), cancellable = true) + private static void cc_generateStructureStarts( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateStructureStarts(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "loadStructureStarts", at = @At("HEAD"), cancellable = true) + private static void cc_loadStructureStarts( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.loadStructureStarts(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateStructureReferences", at = @At("HEAD"), cancellable = true) + private static void cc_generateStructureReferences( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateStructureReferences(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateBiomes", at = @At("HEAD"), cancellable = true) + private static void cc_generateBiomes( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateBiomes(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateNoise", at = @At("HEAD"), cancellable = true) + private static void cc_generateNoise( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateNoise(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateSurface", at = @At("HEAD"), cancellable = true) + private static void cc_generateSurface( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateSurface(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateCarvers", at = @At("HEAD"), cancellable = true) + private static void cc_generateCarvers( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateCarvers(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateFeatures", at = @At("HEAD"), cancellable = true) + private static void cc_generateFeatures( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateFeatures(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "initializeLight", at = @At("HEAD"), cancellable = true) + private static void cc_initializeLight( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.initializeLight(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "light", at = @At("HEAD"), cancellable = true) + private static void cc_light( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.light(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "generateSpawn", at = @At("HEAD"), cancellable = true) + private static void cc_generateSpawn( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.generateSpawn(worldGenContext, step, cache, chunk)); + } + } + + @Inject(method = "full", at = @At("HEAD"), cancellable = true) + private static void cc_full( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk, CallbackInfoReturnable> cir + ) { + if (((CanBeCubic) worldGenContext.level()).cc_isCubic()) { + cir.setReturnValue(CCChunkStatusTasks.full(worldGenContext, step, cache, chunk)); + } + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/package-info.java similarity index 91% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/package-info.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/package-info.java index 3996c70b..a4e85993 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/multiplayer/package-info.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/status/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer; +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.status; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubePyramid$Builder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubePyramid$Builder.java new file mode 100644 index 00000000..1591a54e --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubePyramid$Builder.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube.status; + +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubePyramid; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CubePyramid.Builder.class) +public class MixinCubePyramid$Builder { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubePyramid.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubePyramid.java new file mode 100644 index 00000000..62f87d3a --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubePyramid.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube.status; + +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubePyramid; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CubePyramid.class) +public class MixinCubePyramid { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStatusTasks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStatusTasks.java new file mode 100644 index 00000000..e3791bce --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStatusTasks.java @@ -0,0 +1,26 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube.status; + +import com.llamalad7.mixinextras.sugar.Local; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStatusTasks; +import net.minecraft.world.level.chunk.status.WorldGenContext; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +// Needed for DASM to apply +@Mixin(CubeStatusTasks.class) +public class MixinCubeStatusTasks { + @Dynamic @Redirect(method = "cc_dasm$full", at = @At(value = "INVOKE", target = "Lio/github/opencubicchunks/cubicchunks/util/StaticCache3D;get(II)Ljava/lang/Object;")) + private static Object onFullCube_cacheGet(StaticCache3D instance, int x, int z, @Local(ordinal = 0) CubePos cubePos) { + return instance.get(x, cubePos.getY(), z); + } + + @Dynamic @Redirect(method = "dasm$redirect$lambda$full$2", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/status/WorldGenContext;unsavedListener()Lio/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube$UnsavedListener;")) + private static LevelCube.UnsavedListener onFullCube_worldGenContext_unsavedListener(WorldGenContext instance) { + return cubePos -> {}; // TODO (P2) save/load: this is temporary until WorldGenContext actually has a proper redirect so we can get a cube unsaved listener + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStep$Builder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStep$Builder.java new file mode 100644 index 00000000..92f9e8ce --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStep$Builder.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube.status; + +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CubeStep.Builder.class) +public class MixinCubeStep$Builder { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStep.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStep.java new file mode 100644 index 00000000..4653b7fc --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/MixinCubeStep.java @@ -0,0 +1,22 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube.status; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.profiling.jfr.JvmProfiler; +import net.minecraft.util.profiling.jfr.callback.ProfiledDuration; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +// Needed for DASM to apply +@Mixin(CubeStep.class) +public class MixinCubeStep { + @Dynamic @Redirect(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/jfr/JvmProfiler;onChunkGenerate(Lio/github/opencubicchunks/cc_core/api/CubePos;Lnet/minecraft/resources/ResourceKey;Ljava/lang/String;)Lnet/minecraft/util/profiling/jfr/callback/ProfiledDuration;")) + private ProfiledDuration cc_onApply_profilerCall(JvmProfiler instance, CubePos cubePos, ResourceKey levelResourceKey, String s) { + // TODO we just hack out the profiler stuff for now + return null; + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/package-info.java similarity index 91% rename from src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/package-info.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/package-info.java index 909dea28..dc487086 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/client/renderer/cube/package-info.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/status/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.renderer.cube; +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube.status; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/lighting/MixinLevelLightEngine.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/lighting/MixinLevelLightEngine.java index e788660a..15bdeb2d 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/lighting/MixinLevelLightEngine.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/lighting/MixinLevelLightEngine.java @@ -18,8 +18,8 @@ public class MixinLevelLightEngine { // TODO (P2) lighting - @Inject(method = "lightOnInSection", at = @At("HEAD"), cancellable = true) - private void cc_onLightOnInSection(SectionPos sectionPos, CallbackInfoReturnable cir) { + @Inject(method = "lightOnInColumn", at = @At("HEAD"), cancellable = true) + private void cc_onLightOnInColumn(long columnPos, CallbackInfoReturnable cir) { if (((CanBeCubic) this.levelHeightAccessor).cc_isCubic()) cir.setReturnValue(true); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkInCubicContextSet.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkInCubicContextSet.java new file mode 100644 index 00000000..24c6c7ba --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkInCubicContextSet.java @@ -0,0 +1,7 @@ +package io.github.opencubicchunks.cubicchunks.mixin.dasmsets; + +import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; + +@RedirectSet +public interface ChunkInCubicContextSet extends GlobalSet { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCloSet.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCloSet.java index 6fa28bbb..f2a49ea3 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCloSet.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCloSet.java @@ -7,19 +7,26 @@ import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldToMethodRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.MethodRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect; +import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer; +import io.github.notstirred.dasm.api.annotations.redirect.sets.IntraOwnerContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; import io.github.notstirred.dasm.api.annotations.selector.ConstructorMethodSig; import io.github.notstirred.dasm.api.annotations.selector.FieldSig; import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; +import io.github.opencubicchunks.cubicchunks.movetoforgesourcesetlater.EventConstructorDelegates; import io.github.opencubicchunks.cubicchunks.server.level.CloTrackingView; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import net.minecraft.core.Registry; +import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkTrackingView; +import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; @@ -27,16 +34,18 @@ import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ImposterProtoChunk; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.blending.BlendingData; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.ticks.LevelChunkTicks; import net.minecraft.world.ticks.ProtoChunkTicks; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.event.level.ChunkEvent; /** * Should be used for DASM transforms that work with Clos (i.e. work with both Chunks and Cubes) @@ -75,8 +84,8 @@ interface ChunkAccess_to_CloAccess_redirects { @MethodRedirect(@MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) CloPos cc_getCloPos(); - @MethodRedirect(@MethodSig("getStatus()Lnet/minecraft/world/level/chunk/ChunkStatus;")) - ChunkStatus getStatus(); + @MethodRedirect(@MethodSig("getPersistedStatus()Lnet/minecraft/world/level/chunk/status/ChunkStatus;")) + ChunkStatus getPersistedStatus(); } @TypeRedirect(from = @Ref(LevelChunk.class), to = @Ref(LevelClo.class)) @@ -87,7 +96,7 @@ interface LevelChunk_to_LevelClo_redirects { @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(Level.class), @Ref(CloPos.class) })) static LevelClo create(Level level, ChunkPos pos) { - throw new IllegalStateException("this should never be called"); + throw new DasmFailedToApply(); } @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(Level.class), @Ref(ChunkPos.class), @@ -108,11 +117,11 @@ static LevelClo create(Level level, @Nullable LevelChunkSection[] sections, @Nullable LevelClo.PostLoadProcessor postLoad, @Nullable BlendingData blendingData) { - throw new IllegalStateException("this should never be called"); + throw new DasmFailedToApply(); } @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(ServerLevel.class), @Ref(ProtoChunk.class), @Ref(LevelChunk.PostLoadProcessor.class) })) static LevelClo create(ServerLevel level, ProtoClo clo, @Nullable LevelClo.PostLoadProcessor postLoad) { - throw new IllegalStateException("this should never be called"); + throw new DasmFailedToApply(); } } @@ -127,7 +136,7 @@ interface ProtoChunk_to_ProtoClo_redirects { @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(ChunkPos.class), @Ref(UpgradeData.class), @Ref(LevelHeightAccessor.class), @Ref(Registry.class), @Ref(BlendingData.class) })) static ProtoClo create(CloPos cloPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, @Nullable BlendingData blendingData) { - throw new IllegalStateException("this should never be called"); + throw new DasmFailedToApply(); } @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @@ -149,7 +158,7 @@ static ProtoClo create( Registry biomeRegistry, @Nullable BlendingData blendingData ) { - throw new IllegalStateException("this should never be called"); + throw new DasmFailedToApply(); } } @@ -157,7 +166,7 @@ static ProtoClo create( interface ImposterProtoChunk_to_ImposterProtoClo_redirects { @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(LevelChunk.class), @Ref(boolean.class)})) static ImposterProtoClo create(LevelClo wrapped, boolean allowWrites) { - throw new IllegalStateException("this should never be called"); + throw new DasmFailedToApply(); } @MethodRedirect(@MethodSig("getWrapped()Lnet/minecraft/world/level/chunk/LevelChunk;")) @@ -173,4 +182,33 @@ interface ChunkTrackingView_to_CloTrackingView_redirects { abstract class ChunkTrackingView$Positioned_to_CloTrackingView$Positioned_redirects { } + // Forge stuff + // TODO move to a forge-specific sourceset + @TypeRedirect(from = @Ref(ChunkEvent.Load.class), to = @Ref(Event.class)) + abstract class ChunkEvent$Load_to_Event_redirects { } + @InterOwnerContainer(owner = @Ref(ChunkEvent.Load.class), newOwner = @Ref(EventConstructorDelegates.class)) + abstract class ChunkEvent$Load_delegateConstruction { + @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(LevelChunk.class), @Ref(boolean.class) })) + static native Event create_ChunkEvent$Load(LevelCube levelCube, boolean newChunk); + } + + @TypeRedirect(from = @Ref(ChunkEvent.Unload.class), to = @Ref(Event.class)) + abstract class ChunkEvent$Unload_to_Event_redirects { } + @InterOwnerContainer(owner = @Ref(ChunkEvent.Unload.class), newOwner = @Ref(EventConstructorDelegates.class)) + abstract class ChunkEvent$Unload_delegateConstruction { + @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(LevelChunk.class)})) + static native Event create_ChunkEvent$Unload(LevelCube levelCube); + } + + @IntraOwnerContainer(owner = @Ref(GenerationChunkHolder.class)) + abstract class GenerationChunkHolder_Forge_Jank_redirects { + @FieldToMethodRedirect(value = @FieldSig(name = "currentlyLoading", type = @Ref(LevelChunk.class)), setter = "cc_setCurrentlyLoading") + public native LevelClo cc_getCurrentlyLoading(); + } + // TODO dasm inheritance + @IntraOwnerContainer(owner = @Ref(ChunkHolder.class)) + abstract class ChunkHolder_Forge_Jank_redirects { + @FieldToMethodRedirect(value = @FieldSig(name = "currentlyLoading", type = @Ref(LevelChunk.class)), setter = "cc_setCurrentlyLoading") + public native LevelClo cc_getCurrentlyLoading(); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCubeSet.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCubeSet.java index f537ad8c..02124730 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCubeSet.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ChunkToCubeSet.java @@ -1,10 +1,14 @@ package io.github.opencubicchunks.cubicchunks.mixin.dasmsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; + import io.github.notstirred.dasm.api.annotations.redirect.redirects.ConstructorToFactoryRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldToMethodRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.MethodRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect; +import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.IntraOwnerContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; import io.github.notstirred.dasm.api.annotations.selector.ConstructorMethodSig; @@ -15,21 +19,38 @@ import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderCube; import io.github.opencubicchunks.cubicchunks.client.renderer.cube.RenderRegionCacheCubeInfo; +import io.github.opencubicchunks.cubicchunks.movetoforgesourcesetlater.EventConstructorDelegates; +import io.github.opencubicchunks.cubicchunks.server.level.CubeHolder; +import io.github.opencubicchunks.cubicchunks.server.level.GeneratingCubeMap; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; import io.github.opencubicchunks.cubicchunks.world.level.cube.EmptyLevelCube; import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubePyramid; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStatusTask; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.renderer.chunk.RenderChunk; -import net.minecraft.client.renderer.chunk.RenderRegionCache; +import net.minecraft.server.level.ChunkGenerationTask; import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.GeneratingChunkMap; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.StaticCache2D; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.EmptyLevelChunk; import net.minecraft.world.level.chunk.ImposterProtoChunk; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.status.ChunkPyramid; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatusTask; +import net.minecraft.world.level.chunk.status.ChunkStep; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.event.level.ChunkEvent; /** * Should be used for DASM transforms that work with only Cubes (as opposed to working with both Chunks and Cubes) @@ -115,11 +136,80 @@ abstract class RenderRegionCache$ChunkInfo_to_RenderRegionCacheCubeInfo_redirect ) abstract class ClientChunkCache$Storage_to_ClientCubeCache$Storage_redirects { } + @TypeRedirect(from = @Ref(ChunkStatusTask.class), to = @Ref(CubeStatusTask.class)) + interface ChunkStatusTask_to_CubeStatusTask_redirects { } + + @TypeRedirect(from = @Ref(StaticCache2D.class), to = @Ref(StaticCache3D.class)) + abstract class StaticCache2D_to_StaticCache3D_redirects { } + + @TypeRedirect(from = @Ref(ChunkStep.class), to = @Ref(CubeStep.class)) + abstract class ChunkStep_to_CubeStep_redirects { } + + @TypeRedirect(from = @Ref(ChunkStep.Builder.class), to = @Ref(CubeStep.Builder.class)) + abstract class ChunkStep$Builder_to_CubeStep$Builder_redirects { } + + @TypeRedirect(from = @Ref(ChunkPyramid.class), to = @Ref(CubePyramid.class)) + abstract class ChunkPyramid_to_CubePyramid_redirects { } + + @TypeRedirect(from = @Ref(ChunkPyramid.Builder.class), to = @Ref(CubePyramid.Builder.class)) + abstract class ChunkPyramid$Builder_to_CubePyramid$Builder_redirects { } + + @TypeRedirect(from = @Ref(ChunkHolder.LevelChangeListener.class), to = @Ref(CubeHolder.LevelChangeListener.class)) + interface ChunkHolder$LevelChangeListener_to_CubeHolder$LevelChangeListener_redirects { } + + @TypeRedirect(from = @Ref(ChunkHolder.PlayerProvider.class), to = @Ref(CubeHolder.PlayerProvider.class)) + interface ChunkHolder$PlayerProvider_to_CubeHolder$PlayerProvider_redirects { + @MethodRedirect(@MethodSig("getPlayers(Lnet/minecraft/world/level/ChunkPos;Z)Ljava/util/List;")) + List cc_getPlayers(CubePos pos, boolean boundaryOnly); + } + + @TypeRedirect(from = @Ref(GeneratingChunkMap.class), to = @Ref(GeneratingCubeMap.class)) + interface GeneratingChunkMap_to_GeneratingCubeMap_redirects { + @MethodRedirect(@MethodSig("applyStep(Lnet/minecraft/server/level/GenerationChunkHolder;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;)Ljava/util/concurrent/CompletableFuture;")) + CompletableFuture cc_applyCubeStep(GenerationChunkHolder chunk, CubeStep step, StaticCache3D cache); + + @MethodRedirect(@MethodSig("scheduleGenerationTask(Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/server/level/ChunkGenerationTask;")) + ChunkGenerationTask cc_scheduleGenerationTask(ChunkStatus targetStatus, CubePos pos); + } + + @IntraOwnerContainer(owner = @Ref(ChunkGenerationTask.class)) + abstract class ChunkGenerationTask_redirects { + @FieldToMethodRedirect(@FieldSig(type = @Ref(GeneratingChunkMap.class), name = "chunkMap")) + private native GeneratingCubeMap cc_getGeneratingCubeMap(); + } + + @TypeRedirect(from = @Ref(LevelChunk.UnsavedListener.class), to = @Ref(LevelCube.UnsavedListener.class)) + interface LevelChunk$UnsavedListener_to_LevelCube$UnsavedListener_redirects { } + + @IntraOwnerContainer(owner = @Ref(ChunkHolder.class)) + abstract class ChunkHolder_redirects { + // TODO dasm inheritance + @FieldRedirect(@FieldSig(name = "pos", type = @Ref(ChunkPos.class))) + protected CubePos cc_cubePos; + } + + // Forge stuff // TODO move to a forge-specific sourceset - // getter/setter as a workaround to forge adding a field that needs to be used as a LevelClo in some places and a LevelCube in others + @TypeRedirect(from = @Ref(ChunkEvent.Load.class), to = @Ref(Event.class)) + abstract class ChunkEvent$Load_to_Event_redirects { } + @InterOwnerContainer(owner = @Ref(ChunkEvent.Load.class), newOwner = @Ref(EventConstructorDelegates.class)) + abstract class ChunkEvent$Load_delegateConstruction { + @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(LevelChunk.class), @Ref(boolean.class) })) + static native Event create_ChunkEvent$Load(LevelCube levelCube, boolean newChunk); + } + + @TypeRedirect(from = @Ref(ChunkEvent.Unload.class), to = @Ref(Event.class)) + abstract class ChunkEvent$Unload_to_Event_redirects { } + @InterOwnerContainer(owner = @Ref(ChunkEvent.Unload.class), newOwner = @Ref(EventConstructorDelegates.class)) + abstract class ChunkEvent$Unload_delegateConstruction { + @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(LevelChunk.class)})) + static native Event create_ChunkEvent$Unload(LevelCube levelCube); + } + + // TODO dasm inheritance @IntraOwnerContainer(owner = @Ref(ChunkHolder.class)) abstract class ChunkHolder_Forge_Jank_redirects { - @FieldToMethodRedirect(value = @FieldSig(name = "currentlyLoading", type = @Ref(LevelChunk.class)), setter = "cc_setCurrentlyLoading") - native LevelCube cc_getCurrentlyLoading(); + @FieldRedirect(@FieldSig(name = "currentlyLoading", type = @Ref(LevelChunk.class))) + public LevelCube cc_currentlyLoadingCube; } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ForgeSet.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ForgeSet.java index da0f8e26..3b781ed1 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ForgeSet.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/ForgeSet.java @@ -1,33 +1,9 @@ package io.github.opencubicchunks.cubicchunks.mixin.dasmsets; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.ConstructorToFactoryRedirect; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect; -import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; -import io.github.notstirred.dasm.api.annotations.selector.ConstructorMethodSig; -import io.github.notstirred.dasm.api.annotations.selector.Ref; -import io.github.opencubicchunks.cubicchunks.movetoforgesourcesetlater.EventConstructorDelegates; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.neoforged.bus.api.Event; -import net.neoforged.neoforge.event.level.ChunkEvent; // TODO once redirect sets can be applied conditionally, this should be in the forge sourceset and GlobalSet should no longer extend it @RedirectSet public interface ForgeSet { - @TypeRedirect(from = @Ref(ChunkEvent.Load.class), to = @Ref(Event.class)) - abstract class ChunkEvent$Load_to_Event_redirects { } - @InterOwnerContainer(owner = @Ref(ChunkEvent.Load.class), newOwner = @Ref(EventConstructorDelegates.class)) - abstract class ChunkEvent$Load_delegateConstruction { - @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(ChunkAccess.class), @Ref(boolean.class) })) - static native Event create_ChunkEvent$Load(CloAccess cloAccess, boolean newChunk); - } - @TypeRedirect(from = @Ref(ChunkEvent.Unload.class), to = @Ref(Event.class)) - abstract class ChunkEvent$Unload_to_Event_redirects { } - @InterOwnerContainer(owner = @Ref(ChunkEvent.Unload.class), newOwner = @Ref(EventConstructorDelegates.class)) - abstract class ChunkEvent$Unload_delegateConstruction { - @ConstructorToFactoryRedirect(@ConstructorMethodSig(args = { @Ref(ChunkAccess.class)})) - static native Event create_ChunkEvent$Unload(CloAccess cloAccess); - } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/GlobalSet.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/GlobalSet.java index fc9f45fd..299b3821 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/GlobalSet.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/dasmsets/GlobalSet.java @@ -4,37 +4,29 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Function; -import java.util.function.IntConsumer; -import java.util.function.IntSupplier; import javax.annotation.Nullable; -import com.mojang.datafixers.util.Either; -import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.MethodRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect; -import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.IntraOwnerContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; -import io.github.notstirred.dasm.api.annotations.selector.FieldSig; import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.server.level.CloHolder; import io.github.opencubicchunks.cubicchunks.server.level.CloTrackingView; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; import io.github.opencubicchunks.cubicchunks.server.level.progress.CloProgressListener; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import io.github.opencubicchunks.cubicchunks.world.level.entity.CloStatusUpdateListener; -import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.ChunkTrackingView; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ThreadedLevelLightEngine; import net.minecraft.server.level.TicketType; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.ChunkStatusUpdateListener; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; @@ -45,37 +37,16 @@ */ @RedirectSet public interface GlobalSet extends ForgeSet { - @TypeRedirect(from = @Ref(ChunkHolder.LevelChangeListener.class), to = @Ref(CloHolder.LevelChangeListener.class)) - interface LevelChangeListenerChunkHolder_to_CloHolder_redirects { - @MethodRedirect(@MethodSig("onLevelChange(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/IntSupplier;ILjava/util/function/IntConsumer;)V")) - void cc_onLevelChange(CloPos cloPos, IntSupplier p_140120_, int p_140121_, IntConsumer p_140122_); - } - - @TypeRedirect(from = @Ref(ChunkHolder.PlayerProvider.class), to = @Ref(CloHolder.PlayerProvider.class)) - interface PlayerProviderChunkHolder_to_CloHolder_redirects { } - - @InterOwnerContainer(owner = @Ref(TicketType.class), newOwner = @Ref(CubicTicketType.class)) - abstract class ChunkTicketType_to_CloTicketType_redirects { - @FieldRedirect(@FieldSig(type = @Ref(TicketType.class), name = "PLAYER")) - public static TicketType PLAYER; - @FieldRedirect(@FieldSig(type = @Ref(TicketType.class), name = "FORCED")) - public static TicketType FORCED; - @FieldRedirect(@FieldSig(type = @Ref(TicketType.class), name = "LIGHT")) - public static TicketType LIGHT; - @FieldRedirect(@FieldSig(type = @Ref(TicketType.class), name = "UNKNOWN")) - public static TicketType UNKNOWN; - } - @IntraOwnerContainer(owner = @Ref(ChunkStatus.class)) abstract class ChunkStatus_redirects { @MethodRedirect(@MethodSig("generate(Ljava/util/concurrent/Executor;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager;Lnet/minecraft/server/level/ThreadedLevelLightEngine;Ljava/util/function/Function;Ljava/util/List;)Ljava/util/concurrent/CompletableFuture;")) - public abstract CompletableFuture> cc_generate( + public abstract CompletableFuture> cc_generate( Executor exectutor, ServerLevel level, ChunkGenerator chunkGenerator, StructureTemplateManager structureTemplateManager, ThreadedLevelLightEngine lightEngine, - Function>> task, + Function>> task, List cache ); } @@ -85,7 +56,7 @@ interface ChunkProgressListener_to_CloProgressListener_redirects { @MethodRedirect(@MethodSig("updateSpawnPos(Lnet/minecraft/world/level/ChunkPos;)V")) void cc_updateSpawnPos(CloPos center); - @MethodRedirect(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkStatus;)V")) + @MethodRedirect(@MethodSig("onStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/status/ChunkStatus;)V")) void cc_onStatusChange(CloPos chunkPosition, @Nullable ChunkStatus newStatus); } @@ -97,29 +68,4 @@ interface ChunkTrackingView_to_CloTrackingView_redirects { } @TypeRedirect(from = @Ref(ChunkTrackingView.Positioned.class), to = @Ref(CloTrackingView.Positioned.class)) abstract class ChunkTrackingView$Positioned_to_CloTrackingView$Positioned_redirects { } - - // TODO These need to be specified explicitly for now bc of inheritance jank - @IntraOwnerContainer(owner = @Ref(ChunkMap.DistanceManager.class)) - abstract class ChunkMap$DistanceManager_redirects { - @MethodRedirect(@MethodSig("addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_addTicket(TicketType type, CloPos pos, int level, T value); - - @MethodRedirect(@MethodSig("removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_removeTicket(TicketType type, CloPos pos, int level, T value); - - @MethodRedirect(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_addRegionTicket(TicketType type, CloPos pos, int distance, T value); - - @MethodRedirect(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public abstract void cc_addRegionTicket(TicketType type, CloPos pos, int distance, T value, boolean forceTicks); - - @MethodRedirect(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - public abstract void cc_removeRegionTicket(TicketType type, CloPos pos, int distance, T value); - - @MethodRedirect(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V")) - public abstract void cc_removeRegionTicket(TicketType type, CloPos pos, int distance, T value, boolean forceTicks); - - @MethodRedirect(@MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)V")) - protected abstract void updateCubeForced(CloPos pos, boolean add); - } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/CCCommonHooks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/CCCommonHooks.java new file mode 100644 index 00000000..2da066b1 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/CCCommonHooks.java @@ -0,0 +1,31 @@ +package io.github.opencubicchunks.cubicchunks.movetoforgesourcesetlater; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.neoforged.neoforge.common.CommonHooks; + +@Dasm(GlobalSet.class) +public class CCCommonHooks { + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(CommonHooks.class), method = @MethodSig("onChunkUnload(Lnet/minecraft/world/entity/ai/village/poi/PoiManager;Lnet/minecraft/world/level/chunk/ChunkAccess;)V")) + public static void onCubeUnload(PoiManager poiManager, CubeAccess cubeAccess) { + // TODO (P2) save/load: once PoiManager cubic methods are implemented, this method can be a dasm copy + } + + @AddMethodToSets(sets = ChunkToCloSet.class, owner = @Ref(CommonHooks.class), method = @MethodSig("onChunkUnload(Lnet/minecraft/world/entity/ai/village/poi/PoiManager;Lnet/minecraft/world/level/chunk/ChunkAccess;)V")) + public static void onCloUnload(PoiManager poiManager, CloAccess cloAccess) { + if (cloAccess instanceof CubeAccess cubeAccess) { + onCubeUnload(poiManager, cubeAccess); + } else { + CommonHooks.onChunkUnload(poiManager, ((ChunkAccess) cloAccess)); + } + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/EventConstructorDelegates.java b/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/EventConstructorDelegates.java index 20bd334c..6346c645 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/EventConstructorDelegates.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/movetoforgesourcesetlater/EventConstructorDelegates.java @@ -1,14 +1,15 @@ package io.github.opencubicchunks.cubicchunks.movetoforgesourcesetlater; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import net.minecraft.world.level.chunk.ChunkAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.bus.api.Event; import net.neoforged.neoforge.event.level.ChunkEvent; // In DASM-copied code we redirect forge event construction to factory methods on this class, allowing for easier control (e.g. not firing events for cubic equivalents of vanilla things) public class EventConstructorDelegates { - public static Event create_ChunkEvent$Load(CloAccess cloAccess, boolean newChunk) { - if (cloAccess instanceof ChunkAccess chunk) { + public static Event create_ChunkEvent$Load(LevelClo levelClo, boolean newChunk) { + if (levelClo instanceof LevelChunk chunk) { return new ChunkEvent.Load(chunk, newChunk); } else { // Don't attempt to construct ChunkEvent$Load for cubes @@ -16,8 +17,12 @@ public class EventConstructorDelegates { } } - public static Event create_ChunkEvent$Unload(CloAccess cloAccess) { - if (cloAccess instanceof ChunkAccess chunk) { + public static Event create_ChunkEvent$Load(LevelCube levelCube, boolean newChunk) { + return create_ChunkEvent$Load((LevelClo) levelCube, newChunk); + } + + public static Event create_ChunkEvent$Unload(LevelClo levelClo) { + if (levelClo instanceof LevelChunk chunk) { return new ChunkEvent.Unload(chunk); } else { // Don't attempt to construct ChunkEvent$Unload for cubes @@ -25,6 +30,10 @@ public class EventConstructorDelegates { } } + public static Event create_ChunkEvent$Unload(LevelCube levelCube) { + return create_ChunkEvent$Unload((LevelClo) levelCube); + } + // TODO (P4) we should eventually have CC equivalents for all events // We need this because NeoForge crashes if we pass a null event static class DummyEvent extends Event {} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundForgetLevelCloPacket.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundForgetLevelCloPacket.java index 6bca7aa7..c4ba4a57 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundForgetLevelCloPacket.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundForgetLevelCloPacket.java @@ -1,41 +1,39 @@ package io.github.opencubicchunks.cubicchunks.network; +import static io.github.opencubicchunks.cubicchunks.network.MiscStreamCodecs.CLO_POS_STREAM_CODEC; + import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; +import io.netty.buffer.ByteBuf; import net.minecraft.client.multiplayer.ClientChunkCache; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadHandler; public record CCClientboundForgetLevelCloPacket(CloPos pos) implements CustomPacketPayload { - public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "forget_clo"); - - public CCClientboundForgetLevelCloPacket(FriendlyByteBuf buffer) { - this(CloPos.fromLong(buffer.readLong())); - } + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(CubicChunks.MODID, "forget_clo")); - @Override public void write(FriendlyByteBuf buffer) { - buffer.writeLong(pos.asLong()); - } + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + CLO_POS_STREAM_CODEC, CCClientboundForgetLevelCloPacket::pos, + CCClientboundForgetLevelCloPacket::new + ); - @Override public ResourceLocation id() { - return ID; + @Override public Type type() { + return TYPE; } - public static class Handler implements IPlayPayloadHandler{ - @Override public void handle(CCClientboundForgetLevelCloPacket payload, PlayPayloadContext context) { - var clientChunkCache = ((ClientChunkCache) context.level().get().getChunkSource()); - context.workHandler().execute(() -> { - // TODO P2: queueLightRemoval - look at vanilla packet handler - if (payload.pos.isChunk()) { - clientChunkCache.drop(payload.pos.chunkPos()); - } else { - ((ClientCubeCache) clientChunkCache).cc_drop(payload.pos.cubePos()); - } - }); + public static class Handler implements IPayloadHandler{ + @Override public void handle(CCClientboundForgetLevelCloPacket payload, IPayloadContext context) { + var clientChunkCache = ((ClientChunkCache) context.player().level().getChunkSource()); + // TODO P2: queueLightRemoval - look at vanilla packet handler + if (payload.pos.isChunk()) { + clientChunkCache.drop(payload.pos.chunkPos()); + } else { + ((ClientCubeCache) clientChunkCache).cc_drop(payload.pos.cubePos()); + } } } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelChunkPacket.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelChunkPacket.java index ed1cf55c..16cd35dd 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelChunkPacket.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelChunkPacket.java @@ -1,42 +1,36 @@ package io.github.opencubicchunks.cubicchunks.network; +import static io.github.opencubicchunks.cubicchunks.network.MiscStreamCodecs.CLO_POS_STREAM_CODEC; + import io.github.opencubicchunks.cubicchunks.CubicChunks; +import io.netty.buffer.ByteBuf; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; -import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; - -public class CCClientboundLevelChunkPacket implements CustomPacketPayload { - public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "level_chunk"); +import net.neoforged.neoforge.network.handling.IPayloadHandler; +import net.neoforged.neoforge.network.handling.IPayloadContext; - private final ChunkPos pos; - - public CCClientboundLevelChunkPacket(ChunkPos pos) { - this.pos = pos; - } +public record CCClientboundLevelChunkPacket(ChunkPos pos) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(CubicChunks.MODID, "level_chunk")); - public CCClientboundLevelChunkPacket(FriendlyByteBuf buffer) { - this.pos = new ChunkPos(buffer.readInt(), buffer.readInt()); - } - - @Override public void write(FriendlyByteBuf buffer) { - buffer.writeInt(pos.x); - buffer.writeInt(pos.z); - } + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ChunkPos.STREAM_CODEC, CCClientboundLevelChunkPacket::pos, + CCClientboundLevelChunkPacket::new + ); - @Override public ResourceLocation id() { - return ID; + @Override public Type type() { + return TYPE; } - public static class Handler implements IPlayPayloadHandler { - @Override public void handle(CCClientboundLevelChunkPacket payload, PlayPayloadContext context) { + public static class Handler implements IPayloadHandler { + @Override public void handle(CCClientboundLevelChunkPacket payload, IPayloadContext context) { int x = payload.pos.x; int z = payload.pos.z; // TODO P2 :: This will contain heightmap data and some other stuff - context.workHandler().execute(() -> updateLevelChunk(context.level().get(), x, z)); + updateLevelChunk(context.player().level(), x, z); } private void updateLevelChunk(Level level, int x, int z) { diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubePacketData.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubePacketData.java index 7cc460b7..50ea40d0 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubePacketData.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubePacketData.java @@ -1,15 +1,30 @@ package io.github.opencubicchunks.cubicchunks.network; +import java.util.Arrays; +import java.util.Objects; + +import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.level.chunk.LevelChunkSection; // TODO block entities - see ClientboundLevelChunkPacketData public class CCClientboundLevelCubePacketData { private final byte[] buffer; + public static final StreamCodec STREAM_CODEC = new StreamCodec<>() { + public CCClientboundLevelCubePacketData decode(FriendlyByteBuf buffer) { + return new CCClientboundLevelCubePacketData(buffer); + } + + public void encode(FriendlyByteBuf buffer, CCClientboundLevelCubePacketData data) { + data.write(buffer); + } + }; + public CCClientboundLevelCubePacketData(LevelCube cube) { buffer = new byte[calculateChunkSize(cube)]; extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), cube); @@ -56,4 +71,15 @@ public static void extractChunkData(FriendlyByteBuf buffer, LevelCube cube) { public FriendlyByteBuf getReadBuffer() { return new FriendlyByteBuf(Unpooled.wrappedBuffer(this.buffer)); } + + // Implement .equals for unit testing + @Override public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + CCClientboundLevelCubePacketData that = (CCClientboundLevelCubePacketData) o; + return Objects.deepEquals(buffer, that.buffer); + } + + @Override public int hashCode() { + return Arrays.hashCode(buffer); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubeWithLightPacket.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubeWithLightPacket.java index d571aad3..cfc3b3d6 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubeWithLightPacket.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundLevelCubeWithLightPacket.java @@ -1,63 +1,59 @@ package io.github.opencubicchunks.cubicchunks.network; +import static io.github.opencubicchunks.cubicchunks.network.MiscStreamCodecs.CUBE_POS_STREAM_CODEC; + +import java.util.HashMap; +import java.util.Map; import java.util.function.Consumer; + import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; +import io.github.opencubicchunks.cubicchunks.client.renderer.CubicLevelRenderer; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeSource; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; -import net.minecraft.nbt.CompoundTag; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; -import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.minecraft.world.level.levelgen.Heightmap; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadHandler; // TODO (P2) the name is currently a lie; no light data :) -public class CCClientboundLevelCubeWithLightPacket implements CustomPacketPayload { - public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "level_cube_with_light"); - - private final CubePos pos; - private final CCClientboundLevelCubePacketData chunkData; - - public CCClientboundLevelCubeWithLightPacket(LevelCube cube) { - pos = cube.cc_getCloPos().cubePos(); - chunkData = new CCClientboundLevelCubePacketData(cube); - } +public record CCClientboundLevelCubeWithLightPacket(CubePos pos, CCClientboundLevelCubePacketData cubeData) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(CubicChunks.MODID, "level_cube_with_light")); - public CCClientboundLevelCubeWithLightPacket(final FriendlyByteBuf buffer) { - pos = CubePos.of(buffer.readInt(), buffer.readInt(), buffer.readInt()); - chunkData = new CCClientboundLevelCubePacketData(buffer); - } + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + CUBE_POS_STREAM_CODEC, CCClientboundLevelCubeWithLightPacket::pos, + CCClientboundLevelCubePacketData.STREAM_CODEC, CCClientboundLevelCubeWithLightPacket::cubeData, + CCClientboundLevelCubeWithLightPacket::new + ); - @Override public void write(final FriendlyByteBuf buffer) { - buffer.writeInt(pos.getX()); - buffer.writeInt(pos.getY()); - buffer.writeInt(pos.getZ()); - chunkData.write(buffer); + @Override public Type type() { + return TYPE; } - @Override public ResourceLocation id() { - return ID; - } - - public CCClientboundLevelCubePacketData getChunkData() { - return chunkData; + public CCClientboundLevelCubeWithLightPacket(LevelCube cube) { + this(cube.cc_getCloPos().cubePos(), new CCClientboundLevelCubePacketData(cube)); } - public static class Handler implements IPlayPayloadHandler { + public static class Handler implements IPayloadHandler { @Override - public void handle(CCClientboundLevelCubeWithLightPacket payload, PlayPayloadContext context) { + public void handle(CCClientboundLevelCubeWithLightPacket payload, IPayloadContext context) { int x = payload.pos.getX(); int y = payload.pos.getY(); int z = payload.pos.getZ(); - context.workHandler().execute(() -> this.updateLevelCube(context.level().get(), x, y, z, payload)); + this.updateLevelCube(context.player().level(), x, y, z, payload); } private void updateLevelCube(Level level, int x, int y, int z, CCClientboundLevelCubeWithLightPacket payload) { - // TODO P2 :: The empty compound tag should become a heightmap - CompoundTag heightmap = new CompoundTag(); + // TODO P2 :: The empty map should contain heightmap data + Map heightmaps = new HashMap<>(); // TODO P2 :: No block entity tags consumer Consumer entityTagConsumer = (a) -> {}; @@ -65,9 +61,18 @@ private void updateLevelCube(Level level, int x, int y, int z, CCClientboundLeve ((ClientCubeCache)(level .getChunkSource())) .cc_replaceWithPacketData( - x, y, z, payload.chunkData.getReadBuffer(), heightmap, entityTagConsumer); + x, y, z, payload.cubeData.getReadBuffer(), heightmaps, entityTagConsumer); // TODO P2 :: Vanilla does light updates at this point +// ClientboundLightUpdatePacketData clientboundlightupdatepacketdata = payload.getLightData(); + ((ClientLevel) level).queueLightUpdate(() -> { +// this.applyLightData(i, j, clientboundlightupdatepacketdata, false); + LevelCube levelCube = ((CubeSource) level.getChunkSource()).cc_getCube(x, y, z, false); + if (levelCube != null) { +// this.enableChunkLight(levelCube, i, j); + ((CubicLevelRenderer) Minecraft.getInstance().levelRenderer).cc_onCubeReadyToRender(payload.pos); + } + }); } } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundSetCubeCacheCenterPacket.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundSetCubeCacheCenterPacket.java index 550bffe3..545a4371 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundSetCubeCacheCenterPacket.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCClientboundSetCubeCacheCenterPacket.java @@ -1,36 +1,35 @@ package io.github.opencubicchunks.cubicchunks.network; +import static io.github.opencubicchunks.cubicchunks.network.MiscStreamCodecs.CUBE_POS_STREAM_CODEC; + import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; +import io.netty.buffer.ByteBuf; import net.minecraft.client.multiplayer.ClientChunkCache; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadHandler; +import net.neoforged.neoforge.network.handling.IPayloadContext; public record CCClientboundSetCubeCacheCenterPacket(CubePos pos) implements CustomPacketPayload { - public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "set_cube_cache_center"); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(CubicChunks.MODID, "set_cube_cache_center")); - public CCClientboundSetCubeCacheCenterPacket(FriendlyByteBuf buffer) { - this(CubePos.from(buffer.readLong())); - } - @Override public void write(FriendlyByteBuf buffer) { - buffer.writeLong(pos.asLong()); - } + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + CUBE_POS_STREAM_CODEC, CCClientboundSetCubeCacheCenterPacket::pos, + CCClientboundSetCubeCacheCenterPacket::new + ); - @Override public ResourceLocation id() { - return ID; + @Override public Type type() { + return TYPE; } - public static class Handler implements IPlayPayloadHandler { - @Override public void handle(CCClientboundSetCubeCacheCenterPacket payload, PlayPayloadContext context) { - var clientChunkCache = ((ClientChunkCache) context.level().get().getChunkSource()); - context.workHandler().execute(() -> { - ((ClientCubeCache) clientChunkCache).cc_updateViewCenter(payload.pos.getX(), payload.pos.getY(), payload.pos.getZ()); - }); + public static class Handler implements IPayloadHandler { + @Override public void handle(CCClientboundSetCubeCacheCenterPacket payload, IPayloadContext context) { + var clientChunkCache = ((ClientChunkCache) context.player().level().getChunkSource()); + ((ClientCubeCache) clientChunkCache).cc_updateViewCenter(payload.pos.getX(), payload.pos.getY(), payload.pos.getZ()); } } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCNetworkHandler.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCNetworkHandler.java index 8d3a64ee..d1488fef 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCNetworkHandler.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/CCNetworkHandler.java @@ -2,24 +2,21 @@ import io.github.opencubicchunks.cubicchunks.CubicChunks; import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.Mod; -import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; -import net.neoforged.neoforge.network.registration.IPayloadRegistrar; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; - -// !!! Please minimize the amount of packets that we create until we move to 1.21 !!! -// This is because NeoForge's network is significantly different (and way better!) in 1.21 and beyond - -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) public class CCNetworkHandler { @SubscribeEvent - public static void register(final RegisterPayloadHandlerEvent event) { + public static void register(final RegisterPayloadHandlersEvent event) { // Sets the current network version - final IPayloadRegistrar registrar = event.registrar(CubicChunks.MODID); + final PayloadRegistrar registrar = event.registrar(CubicChunks.MODID); - registrar.play(CCClientboundLevelCubeWithLightPacket.ID, CCClientboundLevelCubeWithLightPacket::new, new CCClientboundLevelCubeWithLightPacket.Handler()); - registrar.play(CCClientboundLevelChunkPacket.ID, CCClientboundLevelChunkPacket::new, new CCClientboundLevelChunkPacket.Handler()); - registrar.play(CCClientboundForgetLevelCloPacket.ID, CCClientboundForgetLevelCloPacket::new, new CCClientboundForgetLevelCloPacket.Handler()); - registrar.play(CCClientboundSetCubeCacheCenterPacket.ID, CCClientboundSetCubeCacheCenterPacket::new, new CCClientboundSetCubeCacheCenterPacket.Handler()); + // Note that by default handlers run on the main thread. + registrar.playToClient(CCClientboundLevelCubeWithLightPacket.TYPE, CCClientboundLevelCubeWithLightPacket.STREAM_CODEC, new CCClientboundLevelCubeWithLightPacket.Handler()); + registrar.playToClient(CCClientboundLevelChunkPacket.TYPE, CCClientboundLevelChunkPacket.STREAM_CODEC, new CCClientboundLevelChunkPacket.Handler()); + registrar.playToClient(CCClientboundForgetLevelCloPacket.TYPE, CCClientboundForgetLevelCloPacket.STREAM_CODEC, new CCClientboundForgetLevelCloPacket.Handler()); + registrar.playToClient(CCClientboundSetCubeCacheCenterPacket.TYPE, CCClientboundSetCubeCacheCenterPacket.STREAM_CODEC, new CCClientboundSetCubeCacheCenterPacket.Handler()); } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/network/MiscStreamCodecs.java b/src/main/java/io/github/opencubicchunks/cubicchunks/network/MiscStreamCodecs.java new file mode 100644 index 00000000..1c6f29b1 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/network/MiscStreamCodecs.java @@ -0,0 +1,29 @@ +package io.github.opencubicchunks.cubicchunks.network; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.StreamCodec; + +public class MiscStreamCodecs { + // TODO where should these go? can't go on CloPos/CubePos because they're in core + public static final StreamCodec CLO_POS_STREAM_CODEC = new StreamCodec<>() { + public CloPos decode(ByteBuf buffer) { + return CloPos.fromLong(buffer.readLong()); + } + + public void encode(ByteBuf buffer, CloPos cloPos) { + buffer.writeLong(cloPos.asLong()); + } + }; + + public static final StreamCodec CUBE_POS_STREAM_CODEC = new StreamCodec<>() { + public CubePos decode(ByteBuf buffer) { + return CubePos.from(buffer.readLong()); + } + + public void encode(ByteBuf buffer, CubePos cubePos) { + buffer.writeLong(cubePos.asLong()); + } + }; +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CCServerPlayer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CCServerPlayer.java new file mode 100644 index 00000000..665253e7 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CCServerPlayer.java @@ -0,0 +1,5 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +public interface CCServerPlayer { + CloTrackingView cc_getCloTrackingView(); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloCollectorFuture.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloCollectorFuture.java deleted file mode 100644 index cac38e66..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloCollectorFuture.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.server.level; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.annotation.Nullable; - -import com.mojang.datafixers.util.Either; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import net.minecraft.server.level.ChunkHolder; - -/** - * A future for loading a list of {@link CloAccess}es, that relies on being externally notified of CloAccesses being loaded, rather than depending on futures for each CloAccess. - *

- * The future completes once every CloAccess has been added. - */ -public class CloCollectorFuture extends CompletableFuture>> { - private final int size; - - private AtomicInteger index = new AtomicInteger(); - - private final Either[] results; - // Vanilla expects that the center chunk is in the middle of the list; this is not the case for cubes, so we manually swap the center cube to the middle - private AtomicInteger indexToBeSwappedWithCenterIndex = new AtomicInteger(-1); - - public CloCollectorFuture(int size) { - this.size = size; - results = new Either[size]; - } - - public void add(Either either, @Nullable Throwable error, boolean isCenterCube) { - if (error != null) { - completeExceptionally(error); - } else { - int i = index.getAndIncrement(); - if (isCenterCube) { - int oldValue = indexToBeSwappedWithCenterIndex.getAndSet(i); - if (oldValue != -1) { - throw new IllegalStateException("Tried to set center cube when center cube was already set"); - } - } - results[i] = either; - } - - if (index.get() >= size) { - done(); - } - } - - private void done() { - int i = indexToBeSwappedWithCenterIndex.get(); - if (i == -1) { - throw new IllegalStateException("All Clos were received but no center cube was set"); - } - int j = results.length / 2; - var results = Arrays.asList(this.results); - Collections.swap(results, i, j); - this.complete(results); - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloGenerationTask.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloGenerationTask.java new file mode 100644 index 00000000..0a233404 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloGenerationTask.java @@ -0,0 +1,7 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import io.github.opencubicchunks.cc_core.world.level.CloPos; + +public interface CloGenerationTask { + CloPos cc_getCloPos(); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloHolder.java index 86e3dde8..fdcdc085 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloHolder.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloHolder.java @@ -1,50 +1,4 @@ package io.github.opencubicchunks.cubicchunks.server.level; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; -import java.util.function.IntConsumer; -import java.util.function.IntSupplier; - -import javax.annotation.Nullable; - -import com.mojang.datafixers.util.Either; -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; -import net.minecraft.server.level.ChunkHolder; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.chunk.ChunkStatus; - -public interface CloHolder { - CloPos cc_getPos(); - - @Nullable LevelClo cc_getTickingChunk(); - - void cc_broadcastChanges(LevelClo clo); - - CompletableFuture> cc_getOrScheduleFuture(ChunkStatus status, ChunkMap map); - - void cc_addSaveDependency(String source, CompletableFuture future); - - /** - * Add a listener that is notified when this CloHolder has reached a given ChunkStatus - * @param status The ChunkStatus to listen for - * @param consumer The listener to call once the status is reached - * @param chunkMap The ChunkMap that manages this CloHolder - */ - void cc_addCloStatusListener(ChunkStatus status, BiConsumer, Throwable> consumer, ChunkMap chunkMap); - - @FunctionalInterface - interface LevelChangeListener { - void cc_onLevelChange(CloPos cloPos, IntSupplier p_140120_, int p_140121_, IntConsumer p_140122_); - } - - interface PlayerProvider { - /** - * Returns the players tracking the given chunk. - */ - List getPlayers(CloPos pos, boolean boundaryOnly); - } +public interface CloHolder extends GenerationCloHolder { } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTaskDispatcher.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTaskDispatcher.java new file mode 100644 index 00000000..8d1b475d --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTaskDispatcher.java @@ -0,0 +1,10 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; + +import io.github.opencubicchunks.cc_core.world.level.CloPos; + +public interface CloTaskDispatcher { + void cc_onLevelChange(CloPos cloPos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTaskPriorityQueueSorter.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTaskPriorityQueueSorter.java deleted file mode 100644 index 3da375a7..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTaskPriorityQueueSorter.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.server.level; - -import java.util.function.IntConsumer; -import java.util.function.IntSupplier; - -import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; -import io.github.opencubicchunks.cc_core.world.level.CloPos; - -public interface CloTaskPriorityQueueSorter { - @UsedFromASM - void cc_onLevelChange(CloPos cloPos, IntSupplier p_140617_, int p_140618_, IntConsumer p_140619_); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTrackingView.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTrackingView.java index 75bed8d3..13e878f7 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTrackingView.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CloTrackingView.java @@ -148,17 +148,16 @@ static boolean cc_isInViewDistance(int centerCubeX, int centerCubeY, int centerC return cc_isWithinDistance(centerCubeX, centerCubeY, centerCubeZ, viewDistanceCubes, cubeX, cubeY, cubeZ, false); } - static boolean cc_isWithinDistance(int centerCubeX, int centerCubeY, int centerCubeZ, int viewDistanceCubes, int cubeX, int cubeY, int cubeZ, boolean increaseRadiusByOne) { - // Mojang does some weird jank, but it's almost identical to just increasing the view distance by 1 - so we do that instead - if (increaseRadiusByOne) viewDistanceCubes++; - int dx = Math.max(0, Math.abs(cubeX - centerCubeX) - 1); - int dy = Math.max(0, Math.abs(cubeY - centerCubeY) - 1); - int dz = Math.max(0, Math.abs(cubeZ - centerCubeZ) - 1); + static boolean cc_isWithinDistance(int centerCubeX, int centerCubeY, int centerCubeZ, int viewDistanceCubes, int cubeX, int cubeY, int cubeZ, boolean includeOuterChunksAdjacentToViewBorder) { + int i = includeOuterChunksAdjacentToViewBorder ? 2 : 1; + int dx = Math.max(0, Math.abs(cubeX - centerCubeX) - i); + int dy = Math.max(0, Math.abs(cubeY - centerCubeY) - i); + int dz = Math.max(0, Math.abs(cubeZ - centerCubeZ) - i); return dx*dx + dy*dy + dz*dz < viewDistanceCubes * viewDistanceCubes; } - static boolean cc_isWithinDistanceCubeColumn(int centerCubeX, int centerCubeZ, int viewDistanceCubes, int cubeX, int cubeZ, boolean increaseRadiusByOne) { - return cc_isWithinDistance(centerCubeX, 0, centerCubeZ, viewDistanceCubes, cubeX, 0, cubeZ, increaseRadiusByOne); + static boolean cc_isWithinDistanceCubeColumn(int centerCubeX, int centerCubeZ, int viewDistanceCubes, int cubeX, int cubeZ, boolean includeOuterChunksAdjacentToViewBorder) { + return cc_isWithinDistance(centerCubeX, 0, centerCubeZ, viewDistanceCubes, cubeX, 0, cubeZ, includeOuterChunksAdjacentToViewBorder); } @Dasm(ChunkToCloSet.class) diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeHolder.java new file mode 100644 index 00000000..ff51ffaf --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeHolder.java @@ -0,0 +1,23 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import java.util.List; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import net.minecraft.server.level.ServerPlayer; + +public interface CubeHolder { + @FunctionalInterface + interface LevelChangeListener { + // FIXME rename to have cc_ prefix again - currently removed due to a DASM bug with how lambdas are handled + void onLevelChange(CubePos cubePos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter); + } + + interface PlayerProvider { + /** + * Returns the players tracking the given cube. + */ + List cc_getPlayers(CubePos pos, boolean boundaryOnly); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeLevel.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeLevel.java new file mode 100644 index 00000000..83db106b --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeLevel.java @@ -0,0 +1,62 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import javax.annotation.Nullable; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubePyramid; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; +import net.minecraft.server.level.ChunkLevel; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.jetbrains.annotations.Contract; + +/** + * Equivalent to {@link ChunkLevel} for cubes; has methods for determining {@link ChunkStatus}es in a radius around a fully loaded cube. + */ +@Dasm(GlobalSet.class) +public class CubeLevel { + private static final int FULL_CHUNK_LEVEL = 33; + private static final CubeStep FULL_CUBE_STEP = CubePyramid.CC_GENERATION_PYRAMID_CUBES.getStepTo(ChunkStatus.FULL); + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkLevel.class), field = @FieldSig(type = @Ref(int.class), name = "RADIUS_AROUND_FULL_CHUNK")) + public static final int RADIUS_AROUND_FULL_CUBE = FULL_CUBE_STEP.accumulatedDependencies().getRadius(); + // TODO not sure if this one should actually be redirected? in some cases we want this MAX_LEVEL, in some cases we want the true MAX_LEVEL, which is greater. + public static final int MAX_LEVEL = FULL_CHUNK_LEVEL + RADIUS_AROUND_FULL_CUBE; + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkLevel.class), method = @MethodSig("generationStatus(I)Lnet/minecraft/world/level/chunk/status/ChunkStatus;")) + @Nullable + public static ChunkStatus cubeGenerationStatus(int level) { + return getStatusAroundFullCube(level - FULL_CHUNK_LEVEL, null); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkLevel.class), method = @MethodSig("getStatusAroundFullChunk(ILnet/minecraft/world/level/chunk/status/ChunkStatus;)Lnet/minecraft/world/level/chunk/status/ChunkStatus;")) + @Nullable + @Contract("_,!null->!null;_,_->_") + public static ChunkStatus getStatusAroundFullCube(int distance, @Nullable ChunkStatus chunkStatus) { + if (distance > RADIUS_AROUND_FULL_CUBE) { + return chunkStatus; + } else { + return distance <= 0 ? ChunkStatus.FULL : FULL_CUBE_STEP.accumulatedDependencies().get(distance); + } + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkLevel.class), method = @MethodSig("getStatusAroundFullChunk(I)Lnet/minecraft/world/level/chunk/status/ChunkStatus;")) + public static ChunkStatus getStatusAroundFullCube(int distance) { + return getStatusAroundFullCube(distance, ChunkStatus.EMPTY); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkLevel.class), method = @MethodSig("byStatus(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)I")) + public static int byCubeStatus(ChunkStatus status) { + return FULL_CHUNK_LEVEL + FULL_CUBE_STEP.getAccumulatedRadiusOf(status); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkLevel.class), method = @MethodSig("isLoaded(I)Z")) + public static boolean isLoadedCube(int level) { + return level <= MAX_LEVEL; + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkMap.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkMap.java index 59693bcd..7177871a 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkMap.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkMap.java @@ -1,7 +1,15 @@ package io.github.opencubicchunks.cubicchunks.server.level; +import io.github.opencubicchunks.cc_core.api.CubePos; +import net.minecraft.server.level.ChunkGenerationTask; +import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.chunk.status.ChunkStatus; public interface CubicChunkMap { + ChunkGenerationTask cc_scheduleGenerationTask(ChunkStatus chunkStatus, CubePos cubePos); + + void cc_onFullChunkStatusChange(CubePos cubePos, FullChunkStatus fullChunkStatus); + boolean cc_isChunkTracked(ServerPlayer player, int x, int y, int z); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicDistanceManager.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicDistanceManager.java deleted file mode 100644 index f9df123c..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicDistanceManager.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.server.level; - -import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.server.level.TicketType; - -public interface CubicDistanceManager { - @UsedFromASM - void cc_addTicket(TicketType type, CloPos pos, int level, T value); - - @UsedFromASM - void cc_removeTicket(TicketType type, CloPos pos, int level, T value); - - @UsedFromASM - void cc_addRegionTicket(TicketType type, CloPos pos, int distance, T value); - - @UsedFromASM - void cc_addRegionTicket(TicketType type, CloPos pos, int distance, T value, boolean forceTicks); - - @UsedFromASM - void cc_removeRegionTicket(TicketType type, CloPos pos, int distance, T value); - - @UsedFromASM - void cc_removeRegionTicket(TicketType type, CloPos pos, int distance, T value, boolean forceTicks); - - @UsedFromASM - boolean cc_runAllUpdates(ChunkMap chunkManager); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java deleted file mode 100644 index 57d46633..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.server.level; - -import java.util.Comparator; - -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.mixin.access.common.TicketTypeAccess; -import net.minecraft.server.level.TicketType; - -public class CubicTicketType { - public static final TicketType PLAYER = create("player", Comparator.comparingLong(CloPos::asLong)); - public static final TicketType FORCED = create("forced", Comparator.comparingLong(CloPos::asLong)); - public static final TicketType LIGHT = create("light", Comparator.comparingLong(CloPos::asLong)); - public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(CloPos::asLong), 1); - - public static TicketType create(String nameIn, Comparator comparator) { - return TicketTypeAccess.cc_createNew(nameIn, comparator, 0L); - } - - public static TicketType create(String nameIn, Comparator comparator, int lifespanIn) { - return TicketTypeAccess.cc_createNew(nameIn, comparator, lifespanIn); - } -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTickingTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTickingTracker.java deleted file mode 100644 index 25545a2c..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTickingTracker.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.server.level; - -import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import net.minecraft.server.level.TicketType; - -public interface CubicTickingTracker { - @UsedFromASM - void cc_addTicket(TicketType type, CloPos cloPos, int ticketLevel, T key); - - @UsedFromASM - void cc_removeTicket(TicketType type, CloPos cloPos, int ticketLevel, T key); - - int cc_getLevel(CloPos cloPos); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/GeneratingCubeMap.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/GeneratingCubeMap.java new file mode 100644 index 00000000..6bf76ff7 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/GeneratingCubeMap.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import java.util.concurrent.CompletableFuture; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStep; +import net.minecraft.server.level.ChunkGenerationTask; +import net.minecraft.server.level.GeneratingChunkMap; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +public interface GeneratingCubeMap extends GeneratingChunkMap { + CompletableFuture cc_applyCubeStep(GenerationChunkHolder chunk, CubeStep step, StaticCache3D cache); + + ChunkGenerationTask cc_scheduleGenerationTask(ChunkStatus targetStatus, CubePos pos); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/GenerationCloHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/GenerationCloHolder.java new file mode 100644 index 00000000..31f391e7 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/GenerationCloHolder.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import javax.annotation.Nullable; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube; + +public interface GenerationCloHolder { + CloPos cc_getCloPos(); + + @Nullable CubePos cc_getCubePos(); + + void cc_replaceProtoCube(ImposterProtoCube cube); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/ServerCubeCache.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/ServerCubeCache.java index d36f387c..463fa4a8 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/ServerCubeCache.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/ServerCubeCache.java @@ -2,32 +2,30 @@ import java.util.concurrent.CompletableFuture; -import com.mojang.datafixers.util.Either; import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeSource; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; -import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkResult; +import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; public interface ServerCubeCache extends CubeSource { - CompletableFuture> cc_getCubeFuture( + CompletableFuture> cc_getCubeFuture( int pX, int pY, int pZ, ChunkStatus pChunkStatus, boolean pLoad ); void cc_blockChanged(BlockPos pos); void cc_onLightUpdate(LightLayer pType, SectionPos pPos); - void cc_addRegionTicket(TicketType pType, CloPos pPos, int pDistance, T pValue); - void cc_addRegionTicket(TicketType p_8388_, CloPos p_8389_, int p_8390_, T p_8391_, boolean forceTicks); + void cc_addTicket(Ticket ticket, CloPos cloPos); - void cc_removeRegionTicket(TicketType pType, CloPos pPos, int pDistance, T pValue); - void cc_removeRegionTicket(TicketType p_8439_, CloPos p_8440_, int p_8441_, T p_8442_, boolean forceTicks); + void cc_addTicketWithRadius(TicketType ticket, CloPos cloPos, int radius); - // Stored on this interface since we can't add inner records in mixins - record CloAndHolder(LevelClo chunk, ChunkHolder holder) {} + void cc_removeTicketWithRadius(TicketType ticket, CloPos cloPos, int radius); + + boolean cc_updateCloForced(CloPos pPos, boolean pAdd); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/SimulationCloTracker.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/SimulationCloTracker.java new file mode 100644 index 00000000..d8ca8eb9 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/SimulationCloTracker.java @@ -0,0 +1,7 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import io.github.opencubicchunks.cc_core.world.level.CloPos; + +public interface SimulationCloTracker { + int cc_getLevel(CloPos cloPos); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/progress/CloProgressListener.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/progress/CloProgressListener.java index e72a5507..7116bd24 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/progress/CloProgressListener.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/progress/CloProgressListener.java @@ -2,11 +2,20 @@ import javax.annotation.Nullable; +import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.world.level.CloPos; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; public interface CloProgressListener { void cc_updateSpawnPos(CloPos center); void cc_onStatusChange(CloPos chunkPosition, @Nullable ChunkStatus newStatus); + + default void cc_updateSpawnPos(CubePos center) { + cc_updateSpawnPos(CloPos.cube(center)); + } + + default void cc_onStatusChange(CubePos chunkPosition, @Nullable ChunkStatus newStatus) { + cc_onStatusChange(CloPos.cube(chunkPosition), newStatus); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/util/StaticCache3D.java b/src/main/java/io/github/opencubicchunks/cubicchunks/util/StaticCache3D.java new file mode 100644 index 00000000..c9e6624d --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/util/StaticCache3D.java @@ -0,0 +1,85 @@ +package io.github.opencubicchunks.cubicchunks.util; + +import java.util.Locale; +import java.util.function.Consumer; + +// TODO move to cc core? - sharing between versions is a bit awkward if vanilla changes it though +// A lot of code duplication here but dasm would be awkward +// FIXME tests? - low priority since it gets hit plenty in integration tests + +/** + * 3D equivalent of vanilla's {@link net.minecraft.util.StaticCache2D}. + */ +public class StaticCache3D { + private final int minX; + private final int minY; + private final int minZ; + private final int sizeX; + private final int sizeY; + private final int sizeZ; + private final Object[] cache; + + public static StaticCache3D create(int centerX, int centerY, int centerZ, int size, StaticCache3D.Initializer initializer) { + int minX = centerX - size; + int minY = centerY - size; + int minZ = centerZ - size; + int diameter = 2 * size + 1; + return new StaticCache3D<>(minX, minY, minZ, diameter, diameter, diameter, initializer); + } + + private StaticCache3D(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ, StaticCache3D.Initializer initializer) { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; + this.cache = new Object[this.sizeX * this.sizeY * this.sizeZ]; + + for (int x = minX; x < minX + sizeX; x++) { + for (int y = minY; y < minY + sizeY; y++) { + for (int z = minZ; z < minZ + sizeZ; z++) { + this.cache[this.getIndex(x, y, z)] = initializer.get(x, y, z); + } + } + } + } + + public void forEach(Consumer action) { + for (Object object : this.cache) { + action.accept((T)object); + } + } + + public T get(int x, int y, int z) { + if (!this.contains(x, y, z)) { + throw new IllegalArgumentException("Requested out of range value (" + x + "," + y + "," + z + ") from " + this); + } else { + return (T)this.cache[this.getIndex(x, y, z)]; + } + } + + public boolean contains(int x, int y, int z) { + int xIndex = x - this.minX; + int yIndex = y - this.minY; + int zIndex = z - this.minZ; + return xIndex >= 0 && xIndex < this.sizeX && yIndex >= 0 && yIndex < this.sizeY && zIndex >= 0 && zIndex < this.sizeZ; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "StaticCache3D[%d, %d, %d, %d, %d, %d]", this.minX, this.minY, this.minZ, this.minX + this.sizeX, this.minY + this.sizeY, this.minZ + this.sizeZ); + } + + private int getIndex(int x, int y, int z) { + int xIndex = x - this.minX; + int yIndex = y - this.minY; + int zIndex = z - this.minZ; + return (xIndex * this.sizeY + yIndex) * this.sizeZ + zIndex; + } + + @FunctionalInterface + public interface Initializer { + T get(int x, int y, int z); + } +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/util/package-info.java similarity index 73% rename from src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/package-info.java rename to src/main/java/io/github/opencubicchunks/cubicchunks/util/package-info.java index 51b30a2f..2345f195 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/package-info.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/util/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.mixin.test.common.server; +package io.github.opencubicchunks.cubicchunks.util; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicLevelReader.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicLevelReader.java index a15aaa5a..94f5cff1 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicLevelReader.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicLevelReader.java @@ -6,7 +6,7 @@ import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; public interface CubicLevelReader extends CubicCollisionGetter { @Nullable CubeAccess cc_getCube(int cubeX, int cubeY, int cubeZ, ChunkStatus chunkStatus, boolean forceLoad); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicTicketStorage.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicTicketStorage.java new file mode 100644 index 00000000..d33f23f2 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/CubicTicketStorage.java @@ -0,0 +1,17 @@ +package io.github.opencubicchunks.cubicchunks.world.level; + +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import net.minecraft.server.level.Ticket; +import net.minecraft.server.level.TicketType; + +public interface CubicTicketStorage { + void cc_addTicketWithRadius(TicketType ticketType, CloPos cloPos, int radius); + + void cc_addTicket(Ticket ticket, CloPos cloPos); + + void cc_removeTicketWithRadius(TicketType ticketType, CloPos cloPos, int radius); + + void cc_removeTicket(Ticket ticket, CloPos cloPos); + + boolean cc_updateChunkForced(CloPos cloPos, boolean add); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunk/status/CCChunkStatusTasks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunk/status/CCChunkStatusTasks.java new file mode 100644 index 00000000..e0447111 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunk/status/CCChunkStatusTasks.java @@ -0,0 +1,128 @@ +package io.github.opencubicchunks.cubicchunks.world.level.chunk.status; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkInCubicContextSet; +import io.github.opencubicchunks.cubicchunks.world.level.cube.status.CubeStatusTasks; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.StaticCache2D; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatusTasks; +import net.minecraft.world.level.chunk.status.ChunkStep; +import net.minecraft.world.level.chunk.status.WorldGenContext; + +/** + * Equivalent of {@link ChunkStatusTasks} for chunks in cubic worlds. + *

+ * See also: {@link CubeStatusTasks} for cube generation tasks. + */ +@Dasm(ChunkInCubicContextSet.class) +public class CCChunkStatusTasks { + @AddTransformToSets(ChunkInCubicContextSet.class) @TransformFromMethod(owner = @Ref(ChunkStatusTasks.class), value = @MethodSig("isLighted(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z")) + private static native boolean isLighted(ChunkAccess chunk); + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("passThrough(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture passThrough( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return CompletableFuture.completedFuture(chunk); + } + + // We skip chunk generation steps in cubic contexts by delegating to passThrough + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateStructureStarts(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;"))// We skip chunk generation steps in cubic contexts by delegating to passThrough + public static CompletableFuture generateStructureStarts( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("loadStructureStarts(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture loadStructureStarts( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateStructureReferences(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateStructureReferences( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateBiomes(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateBiomes( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateNoise(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateNoise( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateSurface(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateSurface( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateCarvers(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateCarvers( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateFeatures(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateFeatures( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("initializeLight(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture initializeLight( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("light(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture light( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateSpawn(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateSpawn( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ) { + return passThrough(worldGenContext, step, cache, chunk); + } + + // We still want to upgrade ProtoChunks to LevelChunks normally + @AddTransformToSets(ChunkInCubicContextSet.class) @TransformFromMethod(owner = @Ref(ChunkStatusTasks.class), value = @MethodSig("full(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static native CompletableFuture full( + WorldGenContext worldGenContext, ChunkStep step, StaticCache2D cache, ChunkAccess chunk + ); + + @AddMethodToSets(sets = ChunkInCubicContextSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("postLoadProtoChunk(Lnet/minecraft/server/level/ServerLevel;Ljava/util/List;)V")) + private static void postLoadProtoChunk(ServerLevel level, List entityTags) { + // Entities should be handled on the cube, not the chunk + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunk/status/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunk/status/package-info.java new file mode 100644 index 00000000..125c2c94 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunk/status/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.world.level.chunk.status; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java index 2ec16283..78f8ef95 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java @@ -13,6 +13,7 @@ import io.github.opencubicchunks.cc_core.world.level.CloPos; import it.unimi.dsi.fastutil.shorts.ShortList; import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.BlockGetter; @@ -25,11 +26,11 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.LightChunk; import net.minecraft.world.level.chunk.StructureAccess; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.gameevent.GameEventListenerRegistry; import net.minecraft.world.level.levelgen.BelowZeroRetrogen; import net.minecraft.world.level.levelgen.Heightmap; @@ -43,7 +44,9 @@ public interface CloAccess extends BlockGetter, BiomeManager.NoiseBiomeSource, LightChunk, StructureAccess { GameEventListenerRegistry getListenerRegistry(int sectionY); - @Nullable BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving); + @Nullable BlockState setBlockState(BlockPos pos, BlockState state); + + @Nullable BlockState setBlockState(BlockPos pos, BlockState state, int flags); void setBlockEntity(BlockEntity blockEntity); @@ -79,11 +82,15 @@ public interface CloAccess extends BlockGetter, BiomeManager.NoiseBiomeSource, L boolean isYSpaceEmpty(int startY, int endY); - void setUnsaved(boolean unsaved); + boolean isSectionEmpty(int sectionY); + + void markUnsaved(); + + boolean tryMarkSaved(); boolean isUnsaved(); - ChunkStatus getStatus(); + ChunkStatus getPersistedStatus(); ChunkStatus getHighestGeneratedStatus(); @@ -93,25 +100,25 @@ public interface CloAccess extends BlockGetter, BiomeManager.NoiseBiomeSource, L ShortList[] getPostProcessing(); - void addPackedPostProcess(short packedPosition, int index); + void addPackedPostProcess(ShortList offsets, int index); void setBlockEntityNbt(CompoundTag tag); @Nullable CompoundTag getBlockEntityNbt(BlockPos pos); - @Nullable CompoundTag getBlockEntityNbtForSaving(BlockPos pos); + @Nullable CompoundTag getBlockEntityNbtForSaving(BlockPos pos, HolderLookup.Provider provider); void findBlocks(Predicate predicate, BiConsumer output); - void findBlocks(java.util.function.BiPredicate predicate, BiConsumer output); - void findBlocks(Predicate predicate, java.util.function.BiPredicate fineFilter, BiConsumer output); TickContainerAccess getBlockTicks(); TickContainerAccess getFluidTicks(); - ChunkAccess.TicksToSave getTicksForSerialization(); + boolean canBeSerialized(); + + ChunkAccess.PackedTicks getTicksForSerialization(long todoNameThis); UpgradeData getUpgradeData(); @@ -119,8 +126,6 @@ public interface CloAccess extends BlockGetter, BiomeManager.NoiseBiomeSource, L @Nullable BlendingData getBlendingData(); - void setBlendingData(BlendingData blendingData); - long getInhabitedTime(); void incrementInhabitedTime(long amount); @@ -150,7 +155,7 @@ public interface CloAccess extends BlockGetter, BiomeManager.NoiseBiomeSource, L // TODO static methods // static ShortList getOrCreateOffsetList(ShortList[] p_62096_, int p_62097_); // -// static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids); +// static record PackedTicks(SerializableTickContainer blocks, SerializableTickContainer fluids); // TODO forge method // @Nullable public net.minecraft.world.level.LevelAccessor getWorldForge() { return null; } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java index ef3ee8c7..345b1306 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java @@ -10,7 +10,6 @@ import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.server.level.FullChunkStatus; @@ -22,6 +21,7 @@ import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.blending.BlendingData; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; @@ -45,19 +45,21 @@ static LevelClo create(Level level, @Nullable PostLoadProcessor postLoad, @Nullable BlendingData blendingData) { if (pos.isCube()) { - return new LevelCube(level, pos.cubePos(), data, blockTicks, fluidTicks, inhabitedTime, sections, postLoad, blendingData); + return new LevelCube(level, pos.cubePos(), data, blockTicks, fluidTicks, inhabitedTime, sections, PostLoadProcessor.forCube(postLoad), blendingData); } else { return (LevelClo) new LevelChunk(level, pos.chunkPos(), data, blockTicks, fluidTicks, inhabitedTime, sections, PostLoadProcessor.forChunk(postLoad), blendingData); } } static LevelClo create(ServerLevel level, ProtoClo clo, @Nullable PostLoadProcessor postLoad) { if (clo instanceof ProtoCube cube) { - return new LevelCube(level, cube, postLoad); + return new LevelCube(level, cube, PostLoadProcessor.forCube(postLoad)); } else { return (LevelClo) new LevelChunk(level, ((ProtoChunk) clo), PostLoadProcessor.forChunk(postLoad)); } } + void cc_setUnsavedListener(LevelClo.UnsavedListener unsavedListener); + FluidState getFluidState(int x, int y, int z); @Nullable @@ -72,7 +74,7 @@ static LevelClo create(ServerLevel level, ProtoClo clo, @Nullable PostLoadProces boolean isEmpty(); void replaceWithPacketData( - FriendlyByteBuf buffer, CompoundTag tag, Consumer outputTagConsumer + FriendlyByteBuf buffer, Map map, Consumer outputTagConsumer ); void replaceBiomes(FriendlyByteBuf buffer); @@ -83,7 +85,7 @@ void replaceWithPacketData( Map getBlockEntities(); - void postProcessGeneration(); + void postProcessGeneration(ServerLevel serverLevel); void unpackTicks(long pos); @@ -113,4 +115,9 @@ interface PostLoadProcessor { return postLoad == null ? null : postLoad::run; } } + + @FunctionalInterface + interface UnsavedListener { + void setUnsaved(CloPos cloPos); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java index d2e08a94..d2c2f064 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java @@ -15,12 +15,11 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.CarvingMask; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.BelowZeroRetrogen; -import net.minecraft.world.level.levelgen.GenerationStep; import net.minecraft.world.level.levelgen.blending.BlendingData; import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.material.Fluid; @@ -59,16 +58,16 @@ static ProtoClo create( List getEntities(); - void setStatus(ChunkStatus status); + void setPersistedStatus(ChunkStatus status); Map getBlockEntityNbts(); @Nullable - CarvingMask getCarvingMask(GenerationStep.Carving step); + CarvingMask getCarvingMask(); - CarvingMask getOrCreateCarvingMask(GenerationStep.Carving step); + CarvingMask getOrCreateCarvingMask(); - void setCarvingMask(GenerationStep.Carving step, CarvingMask carvingMask); + void setCarvingMask(CarvingMask carvingMask); void setLightEngine(LevelLightEngine lightEngine); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java index 3fdb11f3..71df6eac 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java @@ -14,16 +14,18 @@ import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; import io.github.opencubicchunks.cc_core.api.CubePos; -import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cc_core.api.CubicConstants; import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.shorts.ShortList; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; import net.minecraft.nbt.CompoundTag; @@ -37,9 +39,9 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.gameevent.GameEventListenerRegistry; import net.minecraft.world.level.levelgen.BelowZeroRetrogen; import net.minecraft.world.level.levelgen.Heightmap; @@ -56,7 +58,7 @@ public abstract class CubeAccess implements CloAccess { // Fields copied from ChunkAccess, except ChunkPos -> CubePos protected final ShortList[] postProcessing; - protected volatile boolean unsaved; + private volatile boolean unsaved; private volatile boolean isLightCorrect; protected final CubePos cubePos; private long inhabitedTime; @@ -74,7 +76,7 @@ public abstract class CubeAccess implements CloAccess { private final Map structureStarts = Maps.newHashMap(); private final Map structuresRefences = Maps.newHashMap(); protected final Map pendingBlockEntities = Maps.newHashMap(); - protected final Map blockEntities = Maps.newHashMap(); + protected final Map blockEntities = new Object2ObjectOpenHashMap<>(); protected final LevelHeightAccessor levelHeightAccessor; protected final LevelChunkSection[] sections; @@ -121,7 +123,10 @@ private static void replaceMissingSections(Registry biomeRegistry, LevelC ) @Override public native GameEventListenerRegistry getListenerRegistry(int sectionY); - @Override @Nullable public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving); + @TransformFromMethod(owner = @Ref(ChunkAccess.class), value = @MethodSig("setBlockState(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/world/level/block/state/BlockState;")) + @Override @Nullable public native BlockState setBlockState(BlockPos pos, BlockState state); + + @Override @Nullable public abstract BlockState setBlockState(BlockPos pos, BlockState state, int flags); @Override public abstract void setBlockEntity(BlockEntity blockEntity); @@ -134,7 +139,7 @@ private static void replaceMissingSections(Registry biomeRegistry, LevelC } @Override public int getHighestSectionPosition() { - return this.getMinBuildHeight(); + return this.getMinY(); } @TransformFromMethod( @@ -237,11 +242,22 @@ public CubePos cc_getCubePos() { return false; } + @Override public boolean isSectionEmpty(int sectionY) { + // TODO + return false; + } + @TransformFromMethod( - value = @MethodSig("setUnsaved(Z)V"), + value = @MethodSig("markUnsaved()V"), owner = @Ref(ChunkAccess.class) ) - @Override public native void setUnsaved(boolean unsaved); + @Override public native void markUnsaved(); + + @TransformFromMethod( + value = @MethodSig("tryMarkSaved()Z"), + owner = @Ref(ChunkAccess.class) + ) + @Override public native boolean tryMarkSaved(); @TransformFromMethod( value = @MethodSig("isUnsaved()Z"), @@ -249,11 +265,11 @@ public CubePos cc_getCubePos() { ) @Override public native boolean isUnsaved(); - @Override public abstract ChunkStatus getStatus(); + @Override public abstract ChunkStatus getPersistedStatus(); @Override public ChunkStatus getHighestGeneratedStatus() { // In ChunkAccess this method is only used for below-zero retrogen; with no retrogen it does this - return this.getStatus(); + return this.getPersistedStatus(); } @Override public abstract void removeBlockEntity(BlockPos pos); @@ -271,10 +287,10 @@ public CubePos cc_getCubePos() { @Override public native ShortList[] getPostProcessing(); @TransformFromMethod( - value = @MethodSig("addPackedPostProcess(SI)V"), + value = @MethodSig("addPackedPostProcess(Lit/unimi/dsi/fastutil/shorts/ShortList;I)V"), owner = @Ref(ChunkAccess.class) ) - @Override public native void addPackedPostProcess(short packedPosition, int index); + @Override public native void addPackedPostProcess(ShortList offsets, int index); @TransformFromMethod( value = @MethodSig("setBlockEntityNbt(Lnet/minecraft/nbt/CompoundTag;)V"), @@ -288,11 +304,7 @@ public CubePos cc_getCubePos() { ) @Override @Nullable public native CompoundTag getBlockEntityNbt(BlockPos pos); - @TransformFromMethod( - value = @MethodSig("getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;"), - owner = @Ref(ChunkAccess.class) - ) - @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos pos); + @Override @Nullable public abstract CompoundTag getBlockEntityNbtForSaving(BlockPos pos, HolderLookup.Provider provider); @TransformFromMethod( value = @MethodSig("findBlockLightSources(Ljava/util/function/BiConsumer;)V"), @@ -306,12 +318,6 @@ public CubePos cc_getCubePos() { ) @Override public native void findBlocks(Predicate predicate, BiConsumer output); - @TransformFromMethod( - value = @MethodSig("findBlocks(Ljava/util/function/BiPredicate;Ljava/util/function/BiConsumer;)V"), - owner = @Ref(ChunkAccess.class) - ) - @Override public native void findBlocks(java.util.function.BiPredicate predicate, BiConsumer output); - @Override public void findBlocks(Predicate predicate, java.util.function.BiPredicate fineFilter, BiConsumer output) { BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); @@ -343,7 +349,13 @@ public CubePos cc_getCubePos() { @Override public abstract TickContainerAccess getFluidTicks(); - @Override public abstract ChunkAccess.TicksToSave getTicksForSerialization(); + @TransformFromMethod( + value = @MethodSig("canBeSerialized()Z"), + owner = @Ref(ChunkAccess.class) + ) + @Override public native boolean canBeSerialized(); + + @Override public abstract ChunkAccess.PackedTicks getTicksForSerialization(long todoNameThis); @TransformFromMethod( value = @MethodSig("getUpgradeData()Lnet/minecraft/world/level/chunk/UpgradeData;"), @@ -363,12 +375,6 @@ public CubePos cc_getCubePos() { ) @Override @Nullable public native BlendingData getBlendingData(); - @TransformFromMethod( - value = @MethodSig("setBlendingData(Lnet/minecraft/world/level/levelgen/blending/BlendingData;)V"), - owner = @Ref(ChunkAccess.class) - ) - @Override public native void setBlendingData(BlendingData blendingData); - @TransformFromMethod( value = @MethodSig("getInhabitedTime()J"), owner = @Ref(ChunkAccess.class) @@ -406,10 +412,10 @@ public CubePos cc_getCubePos() { @Override public native void setLightCorrect(boolean lightCorrect); @TransformFromMethod( - value = @MethodSig("getMinBuildHeight()I"), + value = @MethodSig("getMinY()I"), owner = @Ref(ChunkAccess.class) ) - @Override public native int getMinBuildHeight(); + @Override public native int getMinY(); @TransformFromMethod( value = @MethodSig("getHeight()I"), diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeSource.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeSource.java index 0d75ffba..19508449 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeSource.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeSource.java @@ -3,7 +3,7 @@ import javax.annotation.Nullable; import io.github.opencubicchunks.cc_core.api.CubePos; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; public interface CubeSource { @Nullable CubeAccess cc_getCube(int x, int y, int z, ChunkStatus status, boolean forceLoad); @@ -18,5 +18,5 @@ public interface CubeSource { int cc_getLoadedCubeCount(); - void cc_updateCubeForced(CubePos cubePos, boolean forced); + boolean cc_updateCubeForced(CubePos cubePos, boolean forced); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/EmptyLevelCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/EmptyLevelCube.java index 8e78f262..2721fa61 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/EmptyLevelCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/EmptyLevelCube.java @@ -3,6 +3,7 @@ import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromClass; import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import net.minecraft.core.Holder; import net.minecraft.world.level.Level; @@ -14,6 +15,6 @@ public class EmptyLevelCube extends LevelCube { public EmptyLevelCube(Level level, CubePos pos, Holder biome) { super(level, pos); - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java index b21b67d2..9715e53f 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java @@ -4,6 +4,7 @@ import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.notstirred.dasm.api.annotations.transform.TransformFromClass; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; @@ -17,12 +18,12 @@ public class ImposterProtoCube extends ProtoCube implements ImposterProtoClo { public ImposterProtoCube(LevelCube wrapped, boolean allowWrites) { super(null, null, null, null, null); - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } // Method is implemented in MixinImposterProtoCube instead, since DASM clears everything in this class. @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ImposterProtoChunk.class), method = @MethodSig("Lnet/minecraft/world/level/chunk/ImposterProtoChunk;getWrapped()Lnet/minecraft/world/level/chunk/LevelChunk;")) @Override public LevelClo cc_getWrappedClo() { - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java index 3d16c9ea..7eaebbe8 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java @@ -2,6 +2,7 @@ import static io.github.notstirred.dasm.api.annotations.transform.Visibility.PUBLIC; +import java.util.Collections; import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; @@ -18,11 +19,15 @@ import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; @@ -31,6 +36,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseRailBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; @@ -38,13 +44,14 @@ import net.minecraft.world.level.block.entity.TickingBlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.gameevent.GameEventListenerRegistry; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.lighting.LightEngine; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.ticks.LevelChunkTicks; @@ -80,6 +87,7 @@ public class LevelCube extends CubeAccess implements LevelClo { private final Int2ObjectMap gameEventListenerRegistrySections; private final LevelChunkTicks blockTicks; private final LevelChunkTicks fluidTicks; + private LevelCube.UnsavedListener unsavedListener = cubePos -> {}; // Constructors mirroring vanilla signatures public LevelCube(Level level, CubePos pos) { @@ -94,10 +102,10 @@ public LevelCube( LevelChunkTicks fluidTicks, long inhabitedTime, @Nullable LevelChunkSection[] sections, - @Nullable LevelClo.PostLoadProcessor postLoad, + @Nullable LevelCube.PostLoadProcessor postLoad, @Nullable BlendingData blendingData ) { - super(pos, data, level, level.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData); + super(pos, data, level, level.registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData); this.level = level; this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>(); @@ -108,12 +116,12 @@ public LevelCube( } } - this.postLoad = LevelClo.PostLoadProcessor.forCube(postLoad); + this.postLoad = postLoad; this.blockTicks = blockTicks; this.fluidTicks = fluidTicks; } - public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadProcessor postLoad) { + public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelCube.PostLoadProcessor postLoad) { this( level, cube.cc_getCubePos(), @@ -126,6 +134,11 @@ public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadP cube.getBlendingData() ); + if (!Collections.disjoint(cube.pendingBlockEntities.keySet(), cube.blockEntities.keySet())) { + LOGGER.error("Cube at {} contains duplicated block entities", cube.cc_getCubePos()); + } + + for(BlockEntity blockentity : cube.getBlockEntities().values()) { this.setBlockEntity(blockentity); } @@ -148,17 +161,31 @@ public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadP this.skyLightSources = cube.skyLightSources; this.setLightCorrect(cube.isLightCorrect()); - this.unsaved = true; + this.markUnsaved(); + } + + public void setUnsavedListener(LevelCube.UnsavedListener unsavedListener) { + this.unsavedListener = unsavedListener; + if (this.isUnsaved()) { + this.unsavedListener.setUnsaved(this.cubePos); + } } + @Override public void cc_setUnsavedListener(LevelClo.UnsavedListener unsavedListener) { + this.setUnsavedListener(cubePos -> unsavedListener.setUnsaved(CloPos.cube(cubePos))); + } + + @TransformFromMethod(value = @MethodSig("markUnsaved()V"), owner = @Ref(LevelChunk.class)) + @Override public native void markUnsaved(); + @TransformFromMethod(value = @MethodSig("getBlockTicks()Lnet/minecraft/world/ticks/TickContainerAccess;"), owner = @Ref(LevelChunk.class)) @Override public native TickContainerAccess getBlockTicks(); @TransformFromMethod(value = @MethodSig("getFluidTicks()Lnet/minecraft/world/ticks/TickContainerAccess;"), owner = @Ref(LevelChunk.class)) @Override public native TickContainerAccess getFluidTicks(); - @TransformFromMethod(value = @MethodSig("getTicksForSerialization()Lnet/minecraft/world/level/chunk/ChunkAccess$TicksToSave;"), owner = @Ref(LevelChunk.class)) - @Override public native ChunkAccess.TicksToSave getTicksForSerialization(); + @TransformFromMethod(value = @MethodSig("getTicksForSerialization(J)Lnet/minecraft/world/level/chunk/ChunkAccess$PackedTicks;"), owner = @Ref(LevelChunk.class)) + @Override public native ChunkAccess.PackedTicks getTicksForSerialization(long gameTime); // TODO should this actually be dasm'd? @TransformFromMethod(value = @MethodSig("getListenerRegistry(I)Lnet/minecraft/world/level/gameevent/GameEventListenerRegistry;"), owner = @Ref(LevelChunk.class)) @@ -176,10 +203,10 @@ public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadP @Override public native FluidState getFluidState(int x, int y, int z); // TODO might be dasm-able eventually, if we get more powerful mixin tools - @Nullable @Override public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) { + @Nullable @Override public BlockState setBlockState(BlockPos pos, BlockState state, int flags) { var chunkSection = this.getSection(Coords.blockToIndex(pos)); - boolean isOnlyAir = chunkSection.hasOnlyAir(); - if (isOnlyAir && state.isAir()) { + boolean wasOnlyAir = chunkSection.hasOnlyAir(); + if (wasOnlyAir && state.isAir()) { return null; } else { int sectionLocalX = pos.getX() & 15; @@ -190,36 +217,67 @@ public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadP return null; } else { var block = state.getBlock(); - // TODO (P2) heightmaps + lighting - see vanilla equivalent to this method + // TODO (P2) heightmaps - see vanilla equivalent to this method + boolean isOnlyAir = chunkSection.hasOnlyAir(); + if (wasOnlyAir != isOnlyAir) { + this.level.getChunkSource().getLightEngine().updateSectionStatus(pos, isOnlyAir); + this.level.getChunkSource().onSectionEmptinessChanged(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getY()), SectionPos.blockToSectionCoord(pos.getZ()), isOnlyAir); + } + + if (LightEngine.hasDifferentLightProperties(this, pos, previousState, state)) { + // TODO (P2) lighting - see vanilla equivalent to this method + } + + boolean flag4 = !previousState.is(block); + boolean flag2 = (flags & 64) != 0; + boolean flag3 = (flags & 256) == 0; + if (flag4 && previousState.hasBlockEntity()) { + if (!this.level.isClientSide && flag3) { + BlockEntity blockentity = this.level.getBlockEntity(pos); + if (blockentity != null) { + blockentity.preRemoveSideEffects(pos, previousState); + } + } - boolean flag2 = previousState.hasBlockEntity(); - if (!this.level.isClientSide) { - previousState.onRemove(this.level, pos, state, isMoving); - } else if ((!previousState.is(block) || !state.hasBlockEntity()) && flag2) { this.removeBlockEntity(pos); } + if ((flag4 || block instanceof BaseRailBlock) && this.level instanceof ServerLevel serverlevel && ((flags & 1) != 0 || flag2)) { + previousState.affectNeighborsAfterRemoval(serverlevel, pos, flag2); + } + if (!chunkSection.getBlockState(sectionLocalX, sectionLocalY, sectionLocalZ).is(block)) { return null; } else { - if (!this.level.isClientSide && !this.level.captureBlockSnapshots) { - state.onPlace(this.level, pos, previousState, isMoving); + if (!this.level.isClientSide && !this.level.captureBlockSnapshots && (flags & 512) == 0) { + state.onPlace(this.level, pos, previousState, flag2); } if (state.hasBlockEntity()) { - BlockEntity blockentity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); - if (blockentity == null) { - blockentity = ((EntityBlock)block).newBlockEntity(pos, state); - if (blockentity != null) { - this.addAndRegisterBlockEntity(blockentity); + BlockEntity blockentity1 = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + if (blockentity1 != null && !blockentity1.isValidBlockState(state)) { + LOGGER.warn( + "Found mismatched block entity @ {}: type = {}, state = {}", + pos, + blockentity1.getType().builtInRegistryHolder().key().location(), + state + ); + this.removeBlockEntity(pos); + blockentity1 = null; + } + + if (blockentity1 == null) { + blockentity1 = ((EntityBlock)block).newBlockEntity(pos, state); + if (blockentity1 != null) { + this.addAndRegisterBlockEntity(blockentity1); } } else { - blockentity.setBlockState(state); - this.updateBlockEntityTicker(blockentity); + blockentity1.setBlockState(state); + this.updateBlockEntityTicker(blockentity1); } } - this.unsaved = true; + this.markUnsaved(); return previousState; } } @@ -254,8 +312,8 @@ public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadP @TransformFromMethod(value = @MethodSig("setBlockEntity(Lnet/minecraft/world/level/block/entity/BlockEntity;)V"), owner = @Ref(LevelChunk.class)) @Override public native void setBlockEntity(BlockEntity blockEntity); - @TransformFromMethod(value = @MethodSig("getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;"), owner = @Ref(LevelChunk.class)) - @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos pos); + @TransformFromMethod(value = @MethodSig("getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/HolderLookup$Provider;)Lnet/minecraft/nbt/CompoundTag;"), owner = @Ref(LevelChunk.class)) + @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos pos, HolderLookup.Provider provider); @TransformFromMethod(value = @MethodSig("removeBlockEntity(Lnet/minecraft/core/BlockPos;)V"), owner = @Ref(LevelChunk.class)) @Override public native void removeBlockEntity(BlockPos pos); @@ -280,7 +338,7 @@ public LevelCube(ServerLevel level, ProtoCube cube, @Nullable LevelClo.PostLoadP public native boolean isEmpty(); public void replaceWithPacketData( - FriendlyByteBuf buffer, CompoundTag tag, Consumer outputTagConsumer + FriendlyByteBuf buffer, Map map, Consumer outputTagConsumer ) { this.clearAllBlockEntities(); @@ -292,10 +350,10 @@ public void replaceWithPacketData( // TODO (P2) lighting // this.initializeLightSources(); - outputTagConsumer.accept((p_187968_, p_187969_, p_187970_) -> { - BlockEntity blockentity = this.getBlockEntity(p_187968_, LevelChunk.EntityCreationType.IMMEDIATE); - if (blockentity != null && p_187970_ != null && blockentity.getType() == p_187969_) { - blockentity.handleUpdateTag(p_187970_); + outputTagConsumer.accept((pos, blockEntityType, tag) -> { + BlockEntity blockentity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE); + if (blockentity != null && tag != null && blockentity.getType() == blockEntityType) { + blockentity.loadWithComponents(tag, this.level.registryAccess()); } }); } @@ -313,7 +371,7 @@ public void replaceWithPacketData( public native Map getBlockEntities(); // TODO P2 or P3 figure this out later - stub method for now - public void postProcessGeneration() { + public void postProcessGeneration(ServerLevel serverLevel) { for (int i = 0; i < this.postProcessing.length; ++i) { if (this.postProcessing[i] != null) { this.postProcessing[i].clear(); @@ -345,8 +403,8 @@ public void registerTickContainerInLevel(ServerLevel level) {} // public native void unregisterTickContainerFromLevel(ServerLevel level); public void unregisterTickContainerFromLevel(ServerLevel level) {} - @TransformFromMethod(value = @MethodSig("getStatus()Lnet/minecraft/world/level/chunk/ChunkStatus;"), owner = @Ref(LevelChunk.class)) - @Override public native ChunkStatus getStatus(); + @TransformFromMethod(value = @MethodSig("getPersistedStatus()Lnet/minecraft/world/level/chunk/status/ChunkStatus;"), owner = @Ref(LevelChunk.class)) + @Override public native ChunkStatus getPersistedStatus(); @TransformFromMethod(value = @MethodSig("getFullStatus()Lnet/minecraft/server/level/FullChunkStatus;"), owner = @Ref(LevelChunk.class)) public native FullChunkStatus getFullStatus(); @@ -407,7 +465,7 @@ class BoundTickingBlockEntity implements TickingBlockEnti private boolean loggedInvalidBlockState; BoundTickingBlockEntity(T blockEntity, BlockEntityTicker ticker) { - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } @Override public native void tick(); @@ -431,7 +489,7 @@ public class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { private TickingBlockEntity ticker; RebindableTickingBlockEntityWrapper(TickingBlockEntity ticker) { - throw new IllegalStateException("DASM failed to apply"); + throw new DasmFailedToApply(); } native void rebind(TickingBlockEntity ticker); @@ -446,4 +504,9 @@ public class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { @Override public native String toString(); } + + @FunctionalInterface + public interface UnsavedListener { + void setUnsaved(CubePos cubePos); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java index 61575bf7..e60fb4ec 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java @@ -14,9 +14,10 @@ import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.shorts.ShortList; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; import net.minecraft.nbt.CompoundTag; @@ -30,12 +31,11 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.CarvingMask; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.BelowZeroRetrogen; -import net.minecraft.world.level.levelgen.GenerationStep; import net.minecraft.world.level.levelgen.blending.BlendingData; import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.material.Fluid; @@ -51,7 +51,7 @@ public class ProtoCube extends CubeAccess implements ProtoClo { private volatile LevelLightEngine lightEngine; private volatile ChunkStatus status; private final List entities; - private final Map carvingMasks; + @Nullable private CarvingMask carvingMask; @Nullable private BelowZeroRetrogen belowZeroRetrogen; private final ProtoChunkTicks blockTicks; @@ -67,7 +67,6 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS super(cubePos, upgradeData, levelHeightAccessor, biomeRegistry, 0L, sections, blendingData); this.status = ChunkStatus.EMPTY; this.entities = Lists.newArrayList(); - this.carvingMasks = new Object2ObjectArrayMap(); this.blockTicks = blockTicks; this.fluidTicks = liquidTicks; } @@ -85,10 +84,10 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS @Override public native TickContainerAccess getFluidTicks(); @TransformFromMethod( - value = @MethodSig("getTicksForSerialization()Lnet/minecraft/world/level/chunk/ChunkAccess$TicksToSave;"), + value = @MethodSig("getTicksForSerialization(J)Lnet/minecraft/world/level/chunk/ChunkAccess$PackedTicks;"), owner = @Ref(ProtoChunk.class) ) - @Override public native ChunkAccess.TicksToSave getTicksForSerialization(); + @Override public native ChunkAccess.PackedTicks getTicksForSerialization(long todoNameThis); // dasm + mixin @TransformFromMethod( @@ -105,7 +104,7 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS @Override public native FluidState getFluidState(BlockPos pos); @Nullable - @Override public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) { + @Override public BlockState setBlockState(BlockPos pos, BlockState state, int flags) { int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); @@ -163,16 +162,16 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS @Override public native List getEntities(); @TransformFromMethod( - value = @MethodSig("getStatus()Lnet/minecraft/world/level/chunk/ChunkStatus;"), + value = @MethodSig("getPersistedStatus()Lnet/minecraft/world/level/chunk/status/ChunkStatus;"), owner = @Ref(ProtoChunk.class) ) - @Override public native ChunkStatus getStatus(); + @Override public native ChunkStatus getPersistedStatus(); @TransformFromMethod( - value = @MethodSig("setStatus(Lnet/minecraft/world/level/chunk/ChunkStatus;)V"), + value = @MethodSig("setPersistedStatus(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)V"), owner = @Ref(ProtoChunk.class) ) - @Override public native void setStatus(ChunkStatus status); + @Override public native void setPersistedStatus(ChunkStatus status); @TransformFromMethod( value = @MethodSig("getNoiseBiome(III)Lnet/minecraft/core/Holder;"), @@ -200,10 +199,10 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS @Override public native void markPosForPostprocessing(BlockPos pos); @TransformFromMethod( - value = @MethodSig("addPackedPostProcess(SI)V"), + value = @MethodSig("addPackedPostProcess(Lit/unimi/dsi/fastutil/shorts/ShortList;I)V"), owner = @Ref(ProtoChunk.class) ) - @Override public native void addPackedPostProcess(short packedPosition, int index); + @Override public native void addPackedPostProcess(ShortList offsets, int index); @TransformFromMethod( value = @MethodSig("getBlockEntityNbts()Ljava/util/Map;"), @@ -212,10 +211,10 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS @Override public native Map getBlockEntityNbts(); @TransformFromMethod( - value = @MethodSig("getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;"), + value = @MethodSig("getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/HolderLookup$Provider;)Lnet/minecraft/nbt/CompoundTag;"), owner = @Ref(ProtoChunk.class) ) - @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos pos); + @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos pos, HolderLookup.Provider provider); @TransformFromMethod( value = @MethodSig("removeBlockEntity(Lnet/minecraft/core/BlockPos;)V"), @@ -224,19 +223,19 @@ public ProtoCube(CubePos cubePos, UpgradeData upgradeData, @Nullable LevelChunkS @Override public native void removeBlockEntity(BlockPos pos); @TransformFromMethod( - value = @MethodSig("getCarvingMask(Lnet/minecraft/world/level/levelgen/GenerationStep$Carving;)Lnet/minecraft/world/level/chunk/CarvingMask;"), + value = @MethodSig("getCarvingMask()Lnet/minecraft/world/level/chunk/CarvingMask;"), owner = @Ref(ProtoChunk.class)) - @Override @Nullable public native CarvingMask getCarvingMask(GenerationStep.Carving step); + @Override @Nullable public native CarvingMask getCarvingMask(); @TransformFromMethod( - value = @MethodSig("getOrCreateCarvingMask(Lnet/minecraft/world/level/levelgen/GenerationStep$Carving;)Lnet/minecraft/world/level/chunk/CarvingMask;"), + value = @MethodSig("getOrCreateCarvingMask()Lnet/minecraft/world/level/chunk/CarvingMask;"), owner = @Ref(ProtoChunk.class)) - @Override public native CarvingMask getOrCreateCarvingMask(GenerationStep.Carving step); + @Override public native CarvingMask getOrCreateCarvingMask(); @TransformFromMethod( - value = @MethodSig("setCarvingMask(Lnet/minecraft/world/level/levelgen/GenerationStep$Carving;Lnet/minecraft/world/level/chunk/CarvingMask;)V"), + value = @MethodSig("setCarvingMask(Lnet/minecraft/world/level/chunk/CarvingMask;)V"), owner = @Ref(ProtoChunk.class)) - @Override public native void setCarvingMask(GenerationStep.Carving step, CarvingMask carvingMask); + @Override public native void setCarvingMask(CarvingMask carvingMask); @TransformFromMethod( value = @MethodSig("setLightEngine(Lnet/minecraft/world/level/lighting/LevelLightEngine;)V"), diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubePyramid.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubePyramid.java new file mode 100644 index 00000000..98463009 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubePyramid.java @@ -0,0 +1,44 @@ +package io.github.opencubicchunks.cubicchunks.world.level.cube.status; + +import com.google.common.collect.ImmutableList; +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromClass; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet; +import net.minecraft.world.level.chunk.status.ChunkPyramid; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStep; + +/** + * {@link ChunkPyramid} represents a chain of {@link ChunkStep}s for either generating or loading a chunk. This class is the equivalent for cubes. + */ +@Dasm(GlobalSet.class) +public record CubePyramid(ImmutableList steps) { + public CubeStep getStepTo(ChunkStatus status) { + return this.steps.get(status.getIndex()); + } + + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkPyramid.class), field = @FieldSig(type = @Ref(ChunkPyramid.class), name = "GENERATION_PYRAMID")) + public static CubePyramid CC_GENERATION_PYRAMID_CUBES; + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkPyramid.class), field = @FieldSig(type = @Ref(ChunkPyramid.class), name = "LOADING_PYRAMID")) + public static CubePyramid CC_LOADING_PYRAMID_CUBES; + + @TransformFromMethod(useRedirectSets = ChunkToCubeSet.class, owner = @Ref(ChunkPyramid.class), value = @MethodSig("()V")) + static void initCubePyramids() { + throw new DasmFailedToApply(); + } + + static { + initCubePyramids(); + } + + @Dasm(ChunkToCubeSet.class) + @TransformFromClass(sets = ChunkToCubeSet.class, value = @Ref(ChunkPyramid.Builder.class)) + public static class Builder {} +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStatusTask.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStatusTask.java new file mode 100644 index 00000000..131815e0 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStatusTask.java @@ -0,0 +1,13 @@ +package io.github.opencubicchunks.cubicchunks.world.level.cube.status; + +import java.util.concurrent.CompletableFuture; + +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.world.level.chunk.status.WorldGenContext; + +@FunctionalInterface +public interface CubeStatusTask { + CompletableFuture doWork(WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStatusTasks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStatusTasks.java new file mode 100644 index 00000000..261d29c0 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStatusTasks.java @@ -0,0 +1,145 @@ +package io.github.opencubicchunks.cubicchunks.world.level.cube.status; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cubicchunks.CubicChunks; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.status.CCChunkStatusTasks; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.chunk.status.ChunkStatusTasks; +import net.minecraft.world.level.chunk.status.WorldGenContext; +/** + * Equivalent of {@link ChunkStatusTasks} for cube generation. + *

+ * See also: {@link CCChunkStatusTasks} for chunk generation tasks in cubic worlds. + */ +@Dasm(ChunkToCubeSet.class) +public class CubeStatusTasks { + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkStatusTasks.class), value = @MethodSig("isLighted(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z")) + private static native boolean isLighted(CubeAccess cube); + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("passThrough(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture passThrough( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return CompletableFuture.completedFuture(cube); + } + + // TODO (P3) we skip cube generation steps for now by delegating to passThrough + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateStructureStarts(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;"))// We skip chunk generation steps in cubic contexts by delegating to passThrough + public static CompletableFuture generateStructureStarts( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("loadStructureStarts(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture loadStructureStarts( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateStructureReferences(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateStructureReferences( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateBiomes(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateBiomes( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateNoise(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateNoise( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + // Temporary basic sinusoidal terrain, so we can generate a simple test world + int amplitude = 20; + var blockPos = new BlockPos.MutableBlockPos(); + var blockState = Blocks.SMOOTH_STONE.defaultBlockState(); + int minY = cube.cc_getCubePos().minCubeY(); + int maxY = Math.min(cube.cc_getCubePos().maxCubeY(), CubicChunks.SUPERFLAT_HEIGHT + amplitude); + int cubeX = cube.cc_getCubePos().minCubeX(); + int cubeZ = cube.cc_getCubePos().minCubeZ(); + for (int y = minY; y <= maxY; y++) { + for (int x = 0; x < CubicConstants.DIAMETER_IN_BLOCKS; x++) { + for (int z = 0; z < CubicConstants.DIAMETER_IN_BLOCKS; z++) { + if (y + Math.round((amplitude*(Math.sin((x+cubeX)/8.0+(z+cubeZ)/21.0)+Math.cos((z+cubeZ)/13.0)))/2.0) <= CubicChunks.SUPERFLAT_HEIGHT) + cube.setBlockState(blockPos.set(x, y, z), blockState); + } + } + } + return CompletableFuture.completedFuture(cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateSurface(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateSurface( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateCarvers(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateCarvers( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateFeatures(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateFeatures( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("initializeLight(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture initializeLight( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("light(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture light( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + @AddMethodToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStatusTasks.class), method = @MethodSig("generateSpawn(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static CompletableFuture generateSpawn( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ) { + return passThrough(worldGenContext, step, cache, cube); + } + + // Upgrade ProtoCube to LevelCube + // dasm + mixin + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkStatusTasks.class), value = @MethodSig("full(Lnet/minecraft/world/level/chunk/status/WorldGenContext;Lnet/minecraft/world/level/chunk/status/ChunkStep;Lnet/minecraft/util/StaticCache2D;Lnet/minecraft/world/level/chunk/ChunkAccess;)Ljava/util/concurrent/CompletableFuture;")) + public static native CompletableFuture full( + WorldGenContext worldGenContext, CubeStep step, StaticCache3D cache, CubeAccess cube + ); + + @AddTransformToSets(ChunkToCubeSet.class) @TransformFromMethod(owner = @Ref(ChunkStatusTasks.class), value = @MethodSig("postLoadProtoChunk(Lnet/minecraft/server/level/ServerLevel;Ljava/util/List;)V")) + private static native void postLoadProtoCube(ServerLevel level, List entityTags); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStep.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStep.java new file mode 100644 index 00000000..cd9dd2e3 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/CubeStep.java @@ -0,0 +1,120 @@ +package io.github.opencubicchunks.cubicchunks.world.level.cube.status; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import javax.annotation.Nullable; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromClass; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.exception.DasmFailedToApply; +import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCubeSet; +import io.github.opencubicchunks.cubicchunks.util.StaticCache3D; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.world.level.chunk.status.ChunkDependencies; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatusTask; +import net.minecraft.world.level.chunk.status.ChunkStep; +import net.minecraft.world.level.chunk.status.WorldGenContext; + +/** + * {@link ChunkStep} represents a single step in either chunk loading or chunk generation. + * This class represents a single step in cube loading or generation. It is identical to {@code ChunkStep}, but stores a {@link CubeStatusTask} instead of a {@link ChunkStatusTask}. + * @param targetStatus The status that this step corresponds to. + * @param directDependencies The dependencies of this individual step, by radius. + * Always includes the parent status at radius zero (except for EMPTY, which has no parent), and may have additional dependencies. + * @param accumulatedDependencies All dependencies needed to reach this step from unloaded, by radius. + * Effectively a combination of the directDependencies of this step and all previous steps. + * @param blockStateWriteRadius The radius of chunks that can receive blockstate writes. 0 if only the center chunk can; -1 if there are no blockstate writes. Always -1 for chunk loading. + * @param task The chunk loading or generation task for this step. + */ +@Dasm(ChunkToCubeSet.class) +@TransformFromClass(sets = ChunkToCubeSet.class, value = @Ref(ChunkStep.class)) +public record CubeStep( + ChunkStatus targetStatus, ChunkDependencies directDependencies, ChunkDependencies accumulatedDependencies, int blockStateWriteRadius, CubeStatusTask task +) { + public int getAccumulatedRadiusOf(ChunkStatus status) { + throw new DasmFailedToApply(); + } + + public CompletableFuture apply(WorldGenContext worldGenContext, StaticCache3D cache, CubeAccess chunk) { + throw new DasmFailedToApply(); + } + + @Dasm(ChunkToCubeSet.class) + public static class Builder { + private final ChunkStatus status; + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStep.Builder.class), field = @FieldSig(type = @Ref(CubeStep.class), name = "parent")) + @Nullable private final CubeStep parent; + private ChunkStatus[] directDependenciesByRadius; + private int blockStateWriteRadius = -1; + @AddFieldToSets(sets = ChunkToCubeSet.class, owner = @Ref(ChunkStep.Builder.class), field = @FieldSig(type = @Ref(ChunkStatusTask.class), name = "task")) + private CubeStatusTask task = (worldGenContext, step, cache, cube) -> CompletableFuture.completedFuture(cube); + + // TODO these should be dasm-copied but for some reason it didn't work when I tried + protected Builder(ChunkStatus status) { + if (status.getParent() != status) { + throw new IllegalArgumentException("Not starting with the first status: " + status); + } else { + this.status = status; + this.parent = null; + this.directDependenciesByRadius = new ChunkStatus[0]; + } + } + + protected Builder(ChunkStatus status, CubeStep parent) { + if (parent.targetStatus.getIndex() != status.getIndex() - 1) { + throw new IllegalArgumentException("Out of order status: " + status); + } else { + this.status = status; + this.parent = parent; + this.directDependenciesByRadius = new ChunkStatus[]{parent.targetStatus}; + } + } + + // TODO could be mixin + DASM + public CubeStep.Builder addRequirement(ChunkStatus status, int radius) { + if (status.isOrAfter(this.status)) { + throw new IllegalArgumentException("Status " + status + " can not be required by " + this.status); + } else { + ChunkStatus[] achunkstatus = this.directDependenciesByRadius; + int i = Coords.sectionToCubeCeil(radius) + 1; + if (i > achunkstatus.length) { + this.directDependenciesByRadius = new ChunkStatus[i]; + Arrays.fill(this.directDependenciesByRadius, status); + } + + for (int j = 0; j < Math.min(i, achunkstatus.length); j++) { + this.directDependenciesByRadius[j] = ChunkStatus.max(achunkstatus[j], status); + } + + return this; + } + } + + // TODO could be mixin + DASM + public CubeStep.Builder blockStateWriteRadius(int blockStateWriteRadius) { + this.blockStateWriteRadius = Coords.sectionToCubeCeil(blockStateWriteRadius); + return this; + } + + @TransformFromMethod(owner = @Ref(ChunkStep.Builder.class), value = @MethodSig("setTask(Lnet/minecraft/world/level/chunk/status/ChunkStatusTask;)Lnet/minecraft/world/level/chunk/status/ChunkStep$Builder;")) + public native CubeStep.Builder setTask(CubeStatusTask task); + + @TransformFromMethod(owner = @Ref(ChunkStep.Builder.class), value = @MethodSig("build()Lnet/minecraft/world/level/chunk/status/ChunkStep;")) + public native CubeStep build(); + + @TransformFromMethod(owner = @Ref(ChunkStep.Builder.class), value = @MethodSig("buildAccumulatedDependencies()[Lnet/minecraft/world/level/chunk/status/ChunkStatus;")) + private native ChunkStatus[] buildAccumulatedDependencies(); + + @TransformFromMethod(owner = @Ref(ChunkStep.Builder.class), value = @MethodSig("getRadiusOfParent(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)I")) + private native int getRadiusOfParent(ChunkStatus status); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/package-info.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/package-info.java new file mode 100644 index 00000000..d0d5ade0 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/status/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.world.level.cube.status; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index ca0c7e94..e795ba96 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,9 +1,10 @@ -public net.minecraft.server.level.DistanceManager$ChunkTicketTracker +public net.minecraft.server.level.ChunkGenerationTask (Lnet/minecraft/server/level/GeneratingChunkMap;Lnet/minecraft/world/level/chunk/status/ChunkStatus;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/util/StaticCache2D;)V public net.minecraft.server.level.DistanceManager$FixedPlayerDistanceChunkTracker public net.minecraft.server.level.DistanceManager$PlayerTicketTracker public net.minecraft.client.multiplayer.ClientChunkCache$Storage public net.minecraft.server.level.ChunkMap$DistanceManager public net.minecraft.server.level.ChunkMap$TrackedEntity +public net.minecraft.server.level.LoadingChunkTracker public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.server.level.ThreadedLevelLightEngine$TaskType public net.minecraft.world.level.levelgen.VerticalAnchor$BelowTop @@ -14,9 +15,12 @@ public net.minecraft.world.ticks.LevelTicks$PosAndContainerConsumer public net.minecraft.client.renderer.chunk.RenderChunk # TODO ATing RenderChunk is unnecessary if we can make the DASM-transformed constructor widen access instead public net.minecraft.client.renderer.chunk.RenderChunk (Lnet/minecraft/world/level/chunk/LevelChunk;)V -public net.minecraft.client.renderer.chunk.RenderChunkRegion (Lnet/minecraft/world/level/Level;II[[Lnet/minecraft/client/renderer/chunk/RenderChunk;)V +public net.minecraft.client.renderer.chunk.RenderChunkRegion (Lnet/minecraft/world/level/Level;II[Lnet/minecraft/client/renderer/chunk/RenderChunk;)V # TODO ATing RenderRegionCache$ChunkInfo is unnecessary if we can make the DASM-transformed constructor widen access instead public net.minecraft.client.renderer.chunk.RenderRegionCache$ChunkInfo (Lnet/minecraft/world/level/chunk/LevelChunk;)V public net.minecraft.client.renderer.SectionOcclusionGraph$GraphEvents +# since SectionOcclusionGraph$GraphEvents is a record, NeoForge requires that we widen its constructor as well +public net.minecraft.client.renderer.SectionOcclusionGraph$GraphEvents (Lit/unimi/dsi/fastutil/longs/LongSet;Ljava/util/concurrent/BlockingQueue;)V public net.minecraft.world.level.LocalMobCapCalculator$MobCounts public net.minecraft.client.gui.screens.worldselection.SwitchGrid +public net.minecraft.util.StaticCache2D (IIIILnet/minecraft/util/StaticCache2D$Initializer;)V diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/neoforge.mods.toml similarity index 100% rename from src/main/resources/META-INF/mods.toml rename to src/main/resources/META-INF/neoforge.mods.toml diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java b/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java index 06244d5e..9bcbd470 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java @@ -14,56 +14,53 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; -import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cc_core.api.CubicConstants; import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.ChunkHolderTestAccess; import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.ChunkMapTestAccess; +import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.GenerationChunkHolderTestAccess; import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.ServerChunkCacheTestAccess; import io.github.opencubicchunks.cubicchunks.server.level.CloHolder; -import io.github.opencubicchunks.cubicchunks.test.LongRunTest; +import io.github.opencubicchunks.cubicchunks.server.level.CubeLevel; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; import io.github.opencubicchunks.cubicchunks.testutils.CloseableReference; +import io.github.opencubicchunks.cubicchunks.testutils.DummyChunkProgressListener; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.progress.ProcessorChunkProgressListener; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.structure.StructureSet; import net.minecraft.world.level.storage.LevelStorageSource; +import net.neoforged.testframework.junit.EphemeralTestServerProvider; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; import org.mockito.Mockito; -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class IntegrationTestCubicChunkMap extends BaseTest { - // TODO can we avoid code duplication between here and IntegrationTestServerCubeCache? - // (they differ only in that DistanceManager construction is mocked here but not in IntegrationTestServerCubeCache) - // these are test classes, it's probably fine? different things need to be mocked in different tests anyway - private CloseableReference createServerChunkCache(boolean vanillaTest) throws IOException, NoSuchFieldException, IllegalAccessException { - // Worldgen internals - var randomStateMockedStatic = Mockito.mockStatic(RandomState.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); - NoiseBasedChunkGenerator noiseBasedChunkGeneratorMock = mock(); - when(noiseBasedChunkGeneratorMock.generatorSettings()).thenReturn(mock()); - if (vanillaTest) { - // These methods are currently only called when running vanilla tests - when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); - when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); - } + private CloseableReference createServerChunkCache(boolean vanillaTest, RegistryAccess registryAccess) throws IOException, NoSuchFieldException, IllegalAccessException { + HolderGetter biome = registryAccess.lookupOrThrow(Registries.BIOME); + HolderGetter structureSet = registryAccess.lookupOrThrow(Registries.STRUCTURE_SET); + HolderGetter placedFeature = registryAccess.lookupOrThrow(Registries.PLACED_FEATURE); + ChunkGenerator flatLevelSource = new FlatLevelSource(FlatLevelGeneratorSettings.getDefault(biome, structureSet, placedFeature)); // Distance manager is responsible for updating chunk levels; we do this manually for testing var distanceManagerMockedConstruction = Mockito.mockConstruction(ChunkMap.DistanceManager.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); @@ -80,6 +77,7 @@ private CloseableReference createServerChunkCache(boolean vani } when(serverLevelMock.getHeight()).thenReturn(384); when(serverLevelMock.getSectionsCount()).thenReturn(24); + when(serverLevelMock.registryAccess()).thenReturn(registryAccess); // We seem to need an actual directory, not a mock LevelStorageSource.LevelStorageAccess levelStorageAccessMock = mock(Mockito.RETURNS_DEEP_STUBS); when(levelStorageAccessMock.getDimensionPath(any())).thenReturn(Files.createTempDirectory("cc_test")); @@ -91,74 +89,27 @@ private CloseableReference createServerChunkCache(boolean vani // We run everything on the main thread as Mockito has race conditions when multiple threads call into it // (which occurs when using RETURNS_DEEP_STUBS) Runnable::run, - noiseBasedChunkGeneratorMock, + flatLevelSource, 10, // server view distance 10, // simulation distance false, // sync - not relevant for tests; false should be faster - // Need to mock an implementation of the interface, so that it also implements CloProgressListener - Mockito.mock(Mockito.RETURNS_DEEP_STUBS), - mock(Mockito.RETURNS_DEEP_STUBS), + new DummyChunkProgressListener(), + (a, b) -> {}, mock(Mockito.RETURNS_DEEP_STUBS) ); var f = serverLevelMock.getClass().getSuperclass().getDeclaredField("chunkSource"); f.setAccessible(true); f.set(serverLevelMock, serverChunkCache); when(serverLevelMock.getChunkSource()).thenReturn(serverChunkCache); - return new CloseableReference<>(serverChunkCache, randomStateMockedStatic, distanceManagerMockedConstruction); - } - - /** - * Load all dependencies for a single chunk at a given status (note that that chunk will only reach the status below) - */ - public void singleChunkAllDependenciesForStatusVanilla(ChunkStatus status) throws Exception { - try(var serverChunkCacheRef = createServerChunkCache(true)) { - var serverChunkCache = serverChunkCacheRef.value(); - var chunkMap = serverChunkCache.chunkMap; - - var centerLevel = ChunkLevel.byStatus(status); - - var radius = ChunkLevel.MAX_LEVEL - centerLevel; - - ChunkHolder centerHolder = null; - - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - var holder = ((ChunkMapTestAccess) chunkMap).invokeUpdateChunkScheduling( - ChunkPos.asLong(x, z), - centerLevel + Math.max(Math.abs(x), Math.abs(z)), - null, - ChunkLevel.MAX_LEVEL + 1 - ); - if (x == 0 && z == 0) centerHolder = holder; - } - } - - var future = ((ChunkMapTestAccess) chunkMap).invokeGetChunkRangeFuture(centerHolder, status.getRange(), - n -> ((ChunkMapTestAccess) chunkMap).invokeGetDependencyStatus(status, n) - ); - - while (!(future.isDone() || future.isCompletedExceptionally())) { - ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); - } - var either = future.get(); - assertTrue(either.left().isPresent(), () -> status + " chunk dependency future Either should be successful, but was " + either.right().get()); - } - } - - private Stream chunkStatuses() { - return ChunkStatus.getStatusList().stream(); - } - - @ParameterizedTest @MethodSource("chunkStatuses") - public void testSingleChunkAllDependenciesForStatusVanilla(ChunkStatus status) throws Exception { - singleChunkAllDependenciesForStatusVanilla(status); + return new CloseableReference<>(serverChunkCache, distanceManagerMockedConstruction); } /** * Load a single chunk at full status */ - @Test public void singleFullChunkVanilla() throws Exception { - try(var serverChunkCacheRef = createServerChunkCache(true)) { + @ExtendWith(EphemeralTestServerProvider.class) + @Test public void singleFullChunkVanilla(MinecraftServer server) throws Exception { + try(var serverChunkCacheRef = createServerChunkCache(false, server.registryAccess())) { var serverChunkCache = serverChunkCacheRef.value(); var chunkMap = serverChunkCache.chunkMap; @@ -176,69 +127,28 @@ public void testSingleChunkAllDependenciesForStatusVanilla(ChunkStatus status) t null, ChunkLevel.MAX_LEVEL + 1 ); + ((GenerationChunkHolderTestAccess) holder).invokeUpdateHighestAllowedStatus(chunkMap); if (x == 0 && z == 0) centerHolder = holder; } } - var future = centerHolder.getOrScheduleFuture(ChunkStatus.FULL, chunkMap); - - while (!(future.isDone() || future.isCompletedExceptionally())) { - ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); - } - var either = future.get(); - assertTrue(either.left().isPresent(), () -> "Full chunk future Either should be successful, but was " + either.right().get()); - assertInstanceOf(LevelChunk.class, either.left().get()); - } - } - - /** - * Load all dependencies for a single chunk at a given status (note that that chunk will only reach the status below) - */ - public void singleChunkAllDependenciesForStatus(ChunkStatus status) throws Exception { - try(var serverChunkCacheRef = createServerChunkCache(false)) { - var serverChunkCache = serverChunkCacheRef.value(); - var chunkMap = serverChunkCache.chunkMap; - - var centerLevel = ChunkLevel.byStatus(status); - - var radius = ChunkLevel.MAX_LEVEL - centerLevel; - - ChunkHolder centerHolder = null; - - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - var holder = ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( - ChunkPos.asLong(x, z), - centerLevel + Math.max(Math.abs(x), Math.abs(z)), - null, - ChunkLevel.MAX_LEVEL + 1 - ); - if (x == 0 && z == 0) centerHolder = holder; - } - } - - var future = ((ChunkMapTestAccess) chunkMap).invokeCc_GetChunkRangeFuture(centerHolder, status.getRange(), - n -> ((ChunkMapTestAccess) chunkMap).invokeGetDependencyStatus(status, n) - ); + var future = chunkMap.prepareAccessibleChunk(centerHolder); while (!(future.isDone() || future.isCompletedExceptionally())) { ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); } - var either = future.get(); - assertTrue(either.left().isPresent(), () -> status + " chunk dependency future Either should be successful, but was " + either.right().get()); + var result = future.get(); + assertTrue(result.isSuccess(), () -> "Full chunk future ChunkResult should be successful, but was " + result.getError()); + assertInstanceOf(LevelChunk.class, result.orElse(null)); } } - @ParameterizedTest @MethodSource("chunkStatuses") - public void testSingleChunkAllDependenciesForStatus(ChunkStatus status) throws Exception { - singleChunkAllDependenciesForStatus(status); - } - /** * Load a single chunk at full status */ - @Test public void singleFullChunk() throws Exception { - try(var serverChunkCacheRef = createServerChunkCache(false)) { + @ExtendWith(EphemeralTestServerProvider.class) + @Test public void singleFullChunk(MinecraftServer server) throws Exception { + try(var serverChunkCacheRef = createServerChunkCache(false, server.registryAccess())) { var serverChunkCache = serverChunkCacheRef.value(); var chunkMap = serverChunkCache.chunkMap; @@ -250,41 +160,41 @@ public void testSingleChunkAllDependenciesForStatus(ChunkStatus status) throws E for (int x = -radius; x <= radius; x++) { for (int z = -radius; z <= radius; z++) { - var holder = ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + var holder = ((ChunkMapTestAccess) chunkMap).invokeUpdateChunkScheduling( ChunkPos.asLong(x, z), centerLevel + Math.max(Math.abs(x), Math.abs(z)), null, ChunkLevel.MAX_LEVEL + 1 ); + ((GenerationChunkHolderTestAccess) holder).invokeUpdateHighestAllowedStatus(chunkMap); if (x == 0 && z == 0) centerHolder = holder; } } - var future = ((ChunkHolderTestAccess) centerHolder).invokeCc_GetOrScheduleFuture(ChunkStatus.FULL, chunkMap); + var future = chunkMap.prepareAccessibleChunk(centerHolder); while (!(future.isDone() || future.isCompletedExceptionally())) { ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); } - var either = future.get(); - assertTrue(either.left().isPresent(), () -> "Full chunk future Either should be successful, but was " + either.right().get()); - assertInstanceOf(LevelChunk.class, either.left().get()); + var result = future.get(); + assertTrue(result.isSuccess(), () -> "Full chunk future ChunkResult should be successful, but was " + result.getError()); + assertInstanceOf(LevelChunk.class, result.orElse(null)); } } - // TODO might want to test 'dependencies for status' for cubes too - /** * Load a single cube at full status */ - @LongRunTest - @Test public void singleFullCube() throws Exception { - try(var serverChunkCacheRef = createServerChunkCache(false)) { +// @LongRunTest + @ExtendWith(EphemeralTestServerProvider.class) + @Test public void singleFullCube(MinecraftServer server) throws Exception { + try(var serverChunkCacheRef = createServerChunkCache(false, server.registryAccess())) { var serverChunkCache = serverChunkCacheRef.value(); var chunkMap = serverChunkCache.chunkMap; - var centerLevel = ChunkLevel.byStatus(ChunkStatus.FULL); + var centerLevel = CubeLevel.byCubeStatus(ChunkStatus.FULL); - var radius = ChunkLevel.MAX_LEVEL - centerLevel; + var radius = CubeLevel.MAX_LEVEL - centerLevel; ChunkHolder centerHolder = null; @@ -295,27 +205,29 @@ public void testSingleChunkAllDependenciesForStatus(ChunkStatus status) throws E int chunkDistance = Math.max(Math.abs(z), Math.abs(x)); for (int sectionZ = 0; sectionZ < CubicConstants.DIAMETER_IN_SECTIONS; sectionZ++) { for (int sectionX = 0; sectionX < CubicConstants.DIAMETER_IN_SECTIONS; sectionX++) { - ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + var holder = ((ChunkMapTestAccess) chunkMap).invokeUpdateChunkScheduling( CloPos.chunkAsLong(Coords.cubeToSection(x, sectionX), Coords.cubeToSection(z, sectionZ)), centerLevel + chunkDistance, null, ChunkLevel.MAX_LEVEL + 1 ); + ((GenerationChunkHolderTestAccess) holder).invokeUpdateHighestAllowedStatus(chunkMap); } } for (int y = -radius; y <= radius; y++) { - var holder = ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + var holder = ((ChunkMapTestAccess) chunkMap).invokeUpdateChunkScheduling( CloPos.cubeAsLong(x, y, z), centerLevel + Math.max(Math.abs(y), chunkDistance), null, ChunkLevel.MAX_LEVEL + 1 ); + ((GenerationChunkHolderTestAccess) holder).invokeUpdateHighestAllowedStatus(chunkMap); if (x == 0 && z == 0 && y == 0) centerHolder = holder; } } } - var future = ((ChunkHolderTestAccess) centerHolder).invokeCc_GetOrScheduleFuture(ChunkStatus.FULL, chunkMap); + var future = chunkMap.prepareAccessibleChunk(centerHolder); Map> chunksByCubeColumn = new HashMap<>(); List cubes = new ArrayList<>(); @@ -325,14 +237,14 @@ public void testSingleChunkAllDependenciesForStatus(ChunkStatus status) throws E mainThreadProcessor.pollTask(); System.out.println(mainThreadProcessor.getPendingTasksCount()); } - var either = future.get(); - assertTrue(either.left().isPresent(), () -> "Full cube future Either should be successful, but was " + either.right().get()); - assertTrue(either.left().get().getStatus().isOrAfter(ChunkStatus.FULL), - () -> "Cube should be at full status, but has status " + either.left().get().getStatus()); - assertInstanceOf(LevelCube.class, either.left().get()); + var result = (ChunkResult) (Object) future.get(); + assertTrue(result.isSuccess(), () -> "Full chunk future ChunkResult should be successful, but was " + result.getError()); + assertTrue(result.orElse(null).getPersistedStatus().isOrAfter(ChunkStatus.FULL), + () -> "Cube should be at full status, but has status " + result.orElse(null).getPersistedStatus()); + assertInstanceOf(LevelCube.class, result.orElse(null)); for (int sectionZ = 0; sectionZ < CubicConstants.DIAMETER_IN_SECTIONS; sectionZ++) { for (int sectionX = 0; sectionX < CubicConstants.DIAMETER_IN_SECTIONS; sectionX++) { - ChunkStatus status = chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(sectionX, sectionZ)).getLastAvailableStatus(); + ChunkStatus status = chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(sectionX, sectionZ)).getPersistedStatus(); int finalSectionX = sectionX; // java why int finalSectionZ = sectionZ; assertTrue(status.isOrAfter(ChunkStatus.FULL), @@ -352,7 +264,7 @@ private static void assertChunkCubeLoadOrder(ChunkMap chunkMap, Map { - CloPos cloPos = ((CloHolder) cloHolder).cc_getPos(); + CloPos cloPos = ((CloHolder) cloHolder).cc_getCloPos(); if (cloPos.isChunk()) { chunksByCubeColumn.computeIfAbsent(cloPos.correspondingCubeCloPos(0), p -> new ArrayList<>()) .add(cloHolder); @@ -363,7 +275,7 @@ private static void assertChunkCubeLoadOrder(ChunkMap chunkMap, Map { - CloPos cubeCloPos = ((CloHolder) cubeHolder).cc_getPos(); + CloPos cubeCloPos = ((CloHolder) cubeHolder).cc_getCloPos(); List chunksInCubeColumn = chunksByCubeColumn.get(cubeCloPos.correspondingCubeCloPos(0)); chunksInCubeColumn.forEach(chunkHolder -> assertChunkHolderValidForCubeHolder(chunkHolder, cubeHolder)); @@ -371,14 +283,14 @@ private static void assertChunkCubeLoadOrder(ChunkMap chunkMap, Map String.format("Chunk (%s) has status null is lower than cube (%s) at status %s", - ((CloHolder) chunkHolder).cc_getPos(), ((CloHolder) cubeHolder).cc_getPos(), cubeStatus) + ((CloHolder) chunkHolder).cc_getCloPos(), ((CloHolder) cubeHolder).cc_getCloPos(), cubeStatus) ); return; } @@ -391,7 +303,7 @@ public static void assertChunkHolderValidForCubeHolder(ChunkHolder chunkHolder, // Neither are null, assert that statuses are valid. assertTrue(chunkStatus.isOrAfter(cubeStatus), () -> String.format("Chunk (%s) at status %s is lower than cube %s at status %s", - ((CloHolder) chunkHolder).cc_getPos(), chunkStatus, ((CloHolder) cubeHolder).cc_getPos(), cubeStatus) + ((CloHolder) chunkHolder).cc_getCloPos(), chunkStatus, ((CloHolder) cubeHolder).cc_getCloPos(), cubeStatus) ); } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestServerCubeCache.java b/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestServerCubeCache.java index 7df67882..40f44817 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestServerCubeCache.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestServerCubeCache.java @@ -21,6 +21,7 @@ import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CanBeCubic; +import io.github.opencubicchunks.cubicchunks.server.level.CubeLevel; import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; import io.github.opencubicchunks.cubicchunks.testutils.CloseableReference; @@ -32,11 +33,10 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; import net.minecraft.server.level.progress.ProcessorChunkProgressListener; -import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.storage.LevelStorageSource; @@ -65,8 +65,8 @@ private CloseableReference createServerChunkCache(boolean vani when(noiseBasedChunkGeneratorMock.generatorSettings()).thenReturn(mock()); if (vanillaTest) { // These methods are currently only called when running vanilla tests - when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); - when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); + when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[3])); + when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[3])); } ServerLevel serverLevelMock; @@ -117,7 +117,7 @@ public void singleGetChunkVanilla(ChunkStatus status) throws Exception { var serverChunkCache = serverChunkCacheRef.value(); var chunkAccess = serverChunkCache.getChunk(0, 0, status, true); assertNotNull(chunkAccess); - assertTrue(chunkAccess.getStatus().isOrAfter(status)); + assertTrue(chunkAccess.getPersistedStatus().isOrAfter(status)); if (status.isOrAfter(ChunkStatus.FULL)) { assertInstanceOf(LevelChunk.class, chunkAccess); } else { @@ -140,7 +140,7 @@ public void getChunkNowVanilla() throws Exception { serverChunkCache.getChunk(pos.x, pos.z, ChunkStatus.FULL, true); var chunkAccess = serverChunkCache.getChunkNow(pos.x, pos.z); assertNotNull(chunkAccess); - assertSame(ChunkStatus.FULL, chunkAccess.getStatus()); + assertSame(ChunkStatus.FULL, chunkAccess.getPersistedStatus()); assertInstanceOf(LevelChunk.class, chunkAccess); // Neighbor chunk @@ -185,7 +185,7 @@ public void singleGetChunk(ChunkStatus status) throws Exception { var serverChunkCache = serverChunkCacheRef.value(); var chunkAccess = serverChunkCache.getChunk(0, 0, status, true); assertNotNull(chunkAccess); - assertTrue(chunkAccess.getStatus().isOrAfter(status)); + assertTrue(chunkAccess.getPersistedStatus().isOrAfter(status)); if (status.isOrAfter(ChunkStatus.FULL)) { assertInstanceOf(LevelChunk.class, chunkAccess); } else { @@ -210,7 +210,7 @@ public void getChunkNow() throws Exception { serverChunkCache.getChunk(5, -123, ChunkStatus.FULL, true); var chunkAccess = serverChunkCache.getChunkNow(5, -123); assertNotNull(chunkAccess); - assertSame(ChunkStatus.FULL, chunkAccess.getStatus()); + assertSame(ChunkStatus.FULL, chunkAccess.getPersistedStatus()); assertInstanceOf(LevelChunk.class, chunkAccess); // Neighbor chunk @@ -252,7 +252,7 @@ public void singleGetCube(ChunkStatus status) throws Exception { var serverChunkCache = ((ServerCubeCache) serverChunkCacheRef.value()); var chunkAccess = serverChunkCache.cc_getCube(0, 0, 0, status, true); assertNotNull(chunkAccess); - assertTrue(chunkAccess.getStatus().isOrAfter(status)); + assertTrue(chunkAccess.getPersistedStatus().isOrAfter(status)); if (status.isOrAfter(ChunkStatus.FULL)) { assertInstanceOf(LevelCube.class, chunkAccess); } else { @@ -280,7 +280,7 @@ public void getCubeNow() throws Exception { cubicServerChunkCache.cc_getCube(cubePos.getX(), cubePos.getY(), cubePos.getZ(), ChunkStatus.FULL, true); var cubeAccess = cubicServerChunkCache.cc_getCubeNow(cubePos.getX(), cubePos.getY(), cubePos.getZ()); assertNotNull(cubeAccess); - assertSame(ChunkStatus.FULL, cubeAccess.getStatus()); + assertSame(ChunkStatus.FULL, cubeAccess.getPersistedStatus()); assertInstanceOf(LevelCube.class, cubeAccess); // check its chunks for (int localChunkX = 0; localChunkX < CubicConstants.DIAMETER_IN_SECTIONS; localChunkX++) { @@ -288,7 +288,7 @@ public void getCubeNow() throws Exception { var chunkPos = cubePos.asChunkPos(localChunkX, localChunkZ); var chunkAccess = serverChunkCache.getChunkNow(chunkPos.x, chunkPos.z); assertNotNull(chunkAccess); - assertSame(ChunkStatus.FULL, chunkAccess.getStatus()); + assertSame(ChunkStatus.FULL, chunkAccess.getPersistedStatus()); assertInstanceOf(LevelChunk.class, chunkAccess); } } @@ -378,13 +378,13 @@ public void hasCube() throws Exception { var cubicServerChunkCache = ((ServerCubeCache) serverChunkCache); var cubeAccess = cubicServerChunkCache.cc_getCube(0, 0, 0, ChunkStatus.FULL, true); assertNotNull(cubeAccess); - assertTrue(cubeAccess.getStatus().isOrAfter(ChunkStatus.FULL)); + assertTrue(cubeAccess.getPersistedStatus().isOrAfter(ChunkStatus.FULL)); assertInstanceOf(LevelCube.class, cubeAccess); - for (int i = 0; i < ChunkStatus.maxDistance(); i++) { - var expectedStatus = ChunkStatus.getStatusAroundFullChunk(i); + for (int i = 0; i < CubeLevel.RADIUS_AROUND_FULL_CUBE; i++) { + var expectedStatus = CubeLevel.getStatusAroundFullCube(i); cubeAccess = cubicServerChunkCache.cc_getCube(i, -i, 0, expectedStatus, false); assertNotNull(cubeAccess); - assertTrue(cubeAccess.getStatus().isOrAfter(expectedStatus)); + assertTrue(cubeAccess.getPersistedStatus().isOrAfter(expectedStatus)); for (int dx = 0; dx < CubicConstants.DIAMETER_IN_SECTIONS; dx++) { for (int dz = 0; dz < CubicConstants.DIAMETER_IN_SECTIONS; dz++) { int x = -i * CubicConstants.DIAMETER_IN_SECTIONS + dx; @@ -393,23 +393,23 @@ public void hasCube() throws Exception { assertTrue(serverChunkCache.hasChunk(x, z)); var chunkAccess = serverChunkCache.getChunk(x, z, expectedStatus, false); assertNotNull(chunkAccess); - assertTrue(chunkAccess.getStatus().isOrAfter(expectedStatus)); + assertTrue(chunkAccess.getPersistedStatus().isOrAfter(expectedStatus)); } } } } } - @Test public void testAddCubicRegionTicket() throws Exception { + @Test public void testAddCubicTicketWithRadius() throws Exception { try(var serverChunkCacheRef = createServerChunkCache(false)) { var serverChunkCache = serverChunkCacheRef.value(); var cubicServerChunkCache = ((ServerCubeCache) serverChunkCache); int spawnRadius = Coords.sectionToCube(11); - cubicServerChunkCache.cc_addRegionTicket(TicketType.START, CloPos.cube(0, 0, 0), spawnRadius, Unit.INSTANCE); + cubicServerChunkCache.cc_addTicketWithRadius(TicketType.START, CloPos.cube(0, 0, 0), spawnRadius); serverChunkCache.tick(()->true, false); var cubeAccess = cubicServerChunkCache.cc_getCube(0, 0, 0, ChunkStatus.FULL, true); assertNotNull(cubeAccess); - assertTrue(cubeAccess.getStatus().isOrAfter(ChunkStatus.FULL)); + assertTrue(cubeAccess.getPersistedStatus().isOrAfter(ChunkStatus.FULL)); assertInstanceOf(LevelCube.class, cubeAccess); } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinDefaultAttributesTest.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinDefaultAttributesTest.java deleted file mode 100644 index 0ce35651..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinDefaultAttributesTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.test.common; - -import com.google.common.collect.ImmutableMap; -import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -import net.minecraft.world.entity.ai.attributes.DefaultAttributes; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -// TODO: Remove this mixin class when NeoForge supports JUnit for tests -// This mixin class is only required due to NeoForge not supporting JUnit for testing yet (a workaround is currently used, involving Loom) -@Mixin(DefaultAttributes.class) -public abstract class MixinDefaultAttributesTest { - @Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/ai/attributes/DefaultAttributes;SUPPLIERS:Ljava/util/Map;", shift = At.Shift.BY, by = -1)) - private static ImmutableMap cubic_chunks_3$fixNeoForgeRegistryError(ImmutableMap.Builder instance) { - return ImmutableMap.builder().build(); - } - - @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;createLivingAttributes()Lnet/minecraft/world/entity/ai/attributes/AttributeSupplier$Builder;")) - private static AttributeSupplier.Builder cubic_chunks_3$fixNeoForgeRegistryError() { - return AttributeSupplier.builder(); - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinEntityTest.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinEntityTest.java deleted file mode 100644 index 10a73dd2..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinEntityTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.test.common; - -import net.minecraft.core.Holder; -import net.minecraft.world.entity.Entity; -import net.neoforged.neoforge.fluids.FluidType; -import org.mockito.Mockito; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -// TODO: Remove this mixin class when NeoForge supports JUnit for tests -// This mixin class is only required due to NeoForge not supporting JUnit for testing yet (a workaround is currently used, involving Loom) -@Mixin(Entity.class) -public abstract class MixinEntityTest { - @Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;forgeFluidTypeOnEyes:Lnet/neoforged/neoforge/fluids/FluidType;", shift = At.Shift.BY, by = -3)) - private Holder cubic_chunks_3$fixNeoForgeRegistryError() { - return Mockito.mock(); - } - - @Inject(method = "toString", at = @At(value = "HEAD"), cancellable = true) - private void cubic_chunks_3$fixNullPointerException(CallbackInfoReturnable cir) { - cir.setReturnValue("MockedEntity"); - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinLivingEntityTest.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinLivingEntityTest.java deleted file mode 100644 index 150b1abd..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinLivingEntityTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.test.common; - -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.ai.attributes.Attribute; -import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -import org.mockito.Mockito; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -// TODO: Remove this mixin class when NeoForge supports JUnit for tests -// This mixin class is only required due to NeoForge not supporting JUnit for testing yet (a workaround is currently used, involving Loom) -@Mixin(LivingEntity.class) -public abstract class MixinLivingEntityTest { - @Inject(method = "createLivingAttributes", at = @At(value = "HEAD"), cancellable = true) - private static void cubic_chunks_3$mockLivingAttributesInitialization(CallbackInfoReturnable cir) { - cir.setReturnValue(Mockito.mock()); - } - - @Inject(method = "getAttributeValue(Lnet/minecraft/world/entity/ai/attributes/Attribute;)D", at = @At(value = "HEAD"), cancellable = true) - private void cubic_chunks_3$fixNeoForgeErrors(Attribute attribute, CallbackInfoReturnable cir) { - cir.setReturnValue(1d); - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinMobTest.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinMobTest.java deleted file mode 100644 index 857ce6d6..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinMobTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.test.common; - -import net.minecraft.world.entity.Mob; -import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -// TODO: Remove this mixin class when NeoForge supports JUnit for tests -// This mixin class is only required due to NeoForge not supporting JUnit for testing yet (a workaround is currently used, involving Loom) -@Mixin(Mob.class) -public abstract class MixinMobTest { - @Inject(method = "createMobAttributes", at = @At(value = "HEAD"), cancellable = true) - private static void cubic_chunks_3$fixNeoForgeErrors(CallbackInfoReturnable cir) { - cir.setReturnValue(AttributeSupplier.builder()); - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinPlayerTest.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinPlayerTest.java deleted file mode 100644 index 54806a0a..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/MixinPlayerTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.test.common; - -import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -import net.minecraft.world.entity.player.Player; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -// TODO: Remove this mixin class when NeoForge supports JUnit for tests -// This mixin class is only required due to NeoForge not supporting JUnit for testing yet (a workaround is currently used, involving Loom) -@Mixin(Player.class) -public abstract class MixinPlayerTest { - @Inject(method = "createAttributes", at = @At(value = "HEAD"), cancellable = true) - private static void cubic_chunks_3$fixNeoForgeErrors(CallbackInfoReturnable cir) { - cir.setReturnValue(AttributeSupplier.builder()); - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java deleted file mode 100644 index b8ef3906..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; - -import java.util.concurrent.CompletableFuture; - -import com.mojang.datafixers.util.Either; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import net.minecraft.server.level.ChunkHolder; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.world.level.chunk.ChunkStatus; -import org.spongepowered.asm.mixin.Dynamic; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(ChunkHolder.class) -public interface ChunkHolderTestAccess { - @Dynamic @Invoker CompletableFuture> invokeCc_GetOrScheduleFuture(ChunkStatus status, ChunkMap map); -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java index f5033d8e..8438191b 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java @@ -1,19 +1,10 @@ package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.IntFunction; - import javax.annotation.Nullable; -import com.mojang.datafixers.util.Either; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; -import org.spongepowered.asm.mixin.Dynamic; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; @@ -23,12 +14,4 @@ public interface ChunkMapTestAccess { @Accessor("visibleChunkMap") Long2ObjectLinkedOpenHashMap visibleChunkMap(); @Invoker @Nullable ChunkHolder invokeUpdateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel); - - @Invoker CompletableFuture, ChunkHolder.ChunkLoadingFailure>> invokeGetChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction statusGetter); - - @Invoker ChunkStatus invokeGetDependencyStatus(ChunkStatus chunkStatus, int p_140264_); - - @Dynamic @Invoker @Nullable ChunkHolder invokeCc_UpdateChunkScheduling(long cloPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel); - - @Dynamic @Invoker CompletableFuture, ChunkHolder.ChunkLoadingFailure>> invokeCc_GetChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction statusGetter); } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/CubicDistanceManagerTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/CubicDistanceManagerTestAccess.java index 84f62c9c..6892edef 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/CubicDistanceManagerTestAccess.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/CubicDistanceManagerTestAccess.java @@ -1,22 +1,11 @@ package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; -import io.github.opencubicchunks.cc_core.world.level.CloPos; import net.minecraft.server.level.DistanceManager; -import net.minecraft.world.level.ChunkPos; -import org.spongepowered.asm.mixin.Dynamic; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(DistanceManager.class) public interface CubicDistanceManagerTestAccess { - @Invoker(value = "updateChunkForced") - void invoke_updateChunkForced(ChunkPos pos, boolean add); - - @Dynamic - @Invoker(value = "cc_updateCubeForced", remap = false) - void invoke_updateCubeForced(CloPos pos, boolean add); - @Accessor(value = "playerTicketManager") DistanceManager.PlayerTicketTracker get_playerTicketManager(); } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/GenerationChunkHolderTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/GenerationChunkHolderTestAccess.java new file mode 100644 index 00000000..7138cb0f --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/GenerationChunkHolderTestAccess.java @@ -0,0 +1,11 @@ +package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; + +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.GenerationChunkHolder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(GenerationChunkHolder.class) +public interface GenerationChunkHolderTestAccess { + @Invoker void invokeUpdateHighestAllowedStatus(ChunkMap chunkMap); +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/multiplayer/TestClientCubeCache.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/multiplayer/TestClientCubeCache.java index f46d9990..597c218a 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/multiplayer/TestClientCubeCache.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/multiplayer/TestClientCubeCache.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Map; import java.util.Random; import io.github.opencubicchunks.cc_core.api.CubePos; @@ -27,6 +28,7 @@ import org.mockito.Mockito; public class TestClientCubeCache extends BaseTest { + @Disabled // TODO disabled until we can apply client-side mixins in tests properly @Test public void basicTests() { ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS); when(((CanBeCubic) clientLevelMock).cc_isCubic()).thenReturn(true); @@ -129,17 +131,17 @@ public void receiveCubePacketTest() { assertThat(clientChunkCache.cc_getLoadedCubeCount()).isEqualTo(0); // Load first cube // TODO (P2) don't pass in dummy args (tag and consumer) once heightmaps etc. are implemented - clientChunkCache.cc_replaceWithPacketData(pos1.getX(), pos1.getY(), pos1.getZ(), packet1.getChunkData().getReadBuffer(), new CompoundTag(), (a)->{}); + clientChunkCache.cc_replaceWithPacketData(pos1.getX(), pos1.getY(), pos1.getZ(), packet1.cubeData().getReadBuffer(), Map.of(), (a)->{}); assertThat(clientChunkCache.cc_getLoadedCubeCount()).isEqualTo(1); assertDeepEquals(clientChunkCache.cc_getCube(pos1.getX(), pos1.getY(), pos1.getZ(), false), cube1); assertThat(clientChunkCache.cc_getCube(pos2.getX(), pos2.getY(), pos2.getZ(), true)).isSameAs(emptyCube); // Load second cube - clientChunkCache.cc_replaceWithPacketData(pos2.getX(), pos2.getY(), pos2.getZ(), packet2.getChunkData().getReadBuffer(), new CompoundTag(), (a)->{}); + clientChunkCache.cc_replaceWithPacketData(pos2.getX(), pos2.getY(), pos2.getZ(), packet2.cubeData().getReadBuffer(), Map.of(), (a)->{}); assertThat(clientChunkCache.cc_getLoadedCubeCount()).isEqualTo(2); assertDeepEquals(clientChunkCache.cc_getCube(pos1.getX(), pos1.getY(), pos1.getZ(), false), cube1); assertDeepEquals(clientChunkCache.cc_getCube(pos2.getX(), pos2.getY(), pos2.getZ(), false), cube2); // Replace cube1 with cube3 - clientChunkCache.cc_replaceWithPacketData(pos1.getX(), pos1.getY(), pos1.getZ(), packet3.getChunkData().getReadBuffer(), new CompoundTag(), (a)->{}); + clientChunkCache.cc_replaceWithPacketData(pos1.getX(), pos1.getY(), pos1.getZ(), packet3.cubeData().getReadBuffer(), Map.of(), (a)->{}); assertThat(clientChunkCache.cc_getLoadedCubeCount()).isEqualTo(2); assertDeepEquals(clientChunkCache.cc_getCube(pos1.getX(), pos1.getY(), pos1.getZ(), false), cube3); assertDeepEquals(clientChunkCache.cc_getCube(pos2.getX(), pos2.getY(), pos2.getZ(), false), cube2); @@ -149,6 +151,7 @@ public void receiveCubePacketTest() { * Test that if a cube position is within the range of storage, then all adjacent chunk positions are also in range */ @Test + @Disabled // TODO disabled until we can apply client-side mixins in tests properly public void testCubeRangeContainedWithinChunkRange() { ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS); when(((CanBeCubic) clientLevelMock).cc_isCubic()).thenReturn(true); diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/renderer/cube/TestRenderCubeRegion.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/renderer/cube/TestRenderCubeRegion.java index 0f0a3054..161cae8b 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/renderer/cube/TestRenderCubeRegion.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/client/renderer/cube/TestRenderCubeRegion.java @@ -21,6 +21,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.material.Fluids; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.mockito.Answers; @@ -38,10 +39,11 @@ private void singleCubeGetBlockState(Random random) { .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); var state = random.nextBoolean() ? Blocks.STONE.defaultBlockState() : Blocks.DIRT.defaultBlockState(); states.put(pos, state); - cube.setBlockState(pos, state, false); + cube.setBlockState(pos, state); } - var arr = new RenderCube[][][] { new RenderCube[][] { new RenderCube[] { new RenderCube(cube) }}}; - var renderCubeRegion = new RenderCubeRegion(levelMock, cubePos.getX(), cubePos.getY(), cubePos.getZ(), arr, null); + var arr = new RenderCube[27]; + arr[RenderCubeRegion.index(-1, -1, -1, 0, 0, 0)] = new RenderCube(cube); + var renderCubeRegion = new RenderCubeRegion(levelMock, cubePos.getX()-1, cubePos.getY()-1, cubePos.getZ()-1, arr, null); for (var pos : states.keySet()) { assertEquals(states.get(pos), renderCubeRegion.getBlockState(pos)); @@ -58,11 +60,12 @@ private void singleCubeGetFluidState(Random random) { var pos = cubePos .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); positions.add(pos); - cube.setBlockState(pos, state, false); + cube.setBlockState(pos, state); } - var arr = new RenderCube[][][] { new RenderCube[][] { new RenderCube[] { new RenderCube(cube) }}}; - var renderCubeRegion = new RenderCubeRegion(levelMock, cubePos.getX(), cubePos.getY(), cubePos.getZ(), arr, null); + var arr = new RenderCube[27]; + arr[RenderCubeRegion.index(-1, -1, -1, 0, 0, 0)] = new RenderCube(cube); + var renderCubeRegion = new RenderCubeRegion(levelMock, cubePos.getX()-1, cubePos.getY()-1, cubePos.getZ()-1, arr, null); for (var pos : positions) { assertEquals(state, renderCubeRegion.getBlockState(pos)); @@ -70,6 +73,7 @@ private void singleCubeGetFluidState(Random random) { } } + @Disabled // TODO disabled until we can apply client-side mixins in tests properly @Test public void testSingleCubeGetBlockAndFluidState() { var random = new Random(-511); for (int i = 0; i < 100; i++) { diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java index 9b3a457b..5db3981a 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java @@ -17,6 +17,7 @@ import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube; +import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; @@ -112,10 +113,10 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC Stream.concat(Stream.of( ChunkAccess.class.getMethod("getPos"), // TODO need to check existence; these would fail on Fabric - ChunkAccess.class.getMethod("getWorldForge"), - ChunkAccess.class.getMethod("readAttachmentsFromNBT", CompoundTag.class), - ChunkAccess.class.getMethod("writeAttachmentsToNBT"), - ChunkAccess.class.getDeclaredMethod("getAttachmentHolder") + ChunkAccess.class.getDeclaredMethod("writeAttachmentsToNBT", HolderLookup.Provider.class), + ChunkAccess.class.getDeclaredMethod("readAttachmentsFromNBT", HolderLookup.Provider.class, CompoundTag.class), + ChunkAccess.class.getDeclaredMethod("getAttachmentHolder"), + ChunkAccess.class.getDeclaredMethod("getLevel") ), Arrays.stream(IAttachmentHolder.class.getMethods())) ); testStaticParity( @@ -131,12 +132,11 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC Stream.concat(Stream.of( ChunkAccess.class.getMethod("getPos"), // TODO need to check existence; these would fail on Fabric - ChunkAccess.class.getMethod("getWorldForge"), - ChunkAccess.class.getMethod("readAttachmentsFromNBT", CompoundTag.class), - ChunkAccess.class.getMethod("writeAttachmentsToNBT"), + ChunkAccess.class.getDeclaredMethod("writeAttachmentsToNBT", HolderLookup.Provider.class), + ChunkAccess.class.getDeclaredMethod("readAttachmentsFromNBT", HolderLookup.Provider.class, CompoundTag.class), ChunkAccess.class.getDeclaredMethod("getAttachmentHolder"), - LevelChunk.class.getMethod("getWorldForge"), - LevelChunk.class.getMethod("getAuxLightManager", ChunkPos.class) + LevelChunk.class.getMethod("getAuxLightManager", ChunkPos.class), + LevelChunk.class.getMethod("setUnsavedListener", LevelChunk.UnsavedListener.class) ), Arrays.stream(IAttachmentHolder.class.getMethods())) ); testStaticParity( @@ -152,10 +152,10 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC Stream.concat(Stream.of( ChunkAccess.class.getMethod("getPos"), // TODO need to check existence; these would fail on Fabric - ChunkAccess.class.getMethod("getWorldForge"), - ChunkAccess.class.getMethod("readAttachmentsFromNBT", CompoundTag.class), - ChunkAccess.class.getMethod("writeAttachmentsToNBT"), - ChunkAccess.class.getDeclaredMethod("getAttachmentHolder") + ChunkAccess.class.getDeclaredMethod("writeAttachmentsToNBT", HolderLookup.Provider.class), + ChunkAccess.class.getDeclaredMethod("readAttachmentsFromNBT", HolderLookup.Provider.class, CompoundTag.class), + ChunkAccess.class.getDeclaredMethod("getAttachmentHolder"), + ChunkAccess.class.getDeclaredMethod("getLevel") ), Arrays.stream(IAttachmentHolder.class.getMethods())) // IAttachmentHolder.class.getMethods() ); @@ -173,10 +173,10 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC ChunkAccess.class.getMethod("getPos"), ImposterProtoChunk.class.getMethod("getWrapped"), // TODO need to check existence; these would fail on Fabric - ChunkAccess.class.getMethod("getWorldForge"), - ChunkAccess.class.getMethod("readAttachmentsFromNBT", CompoundTag.class), - ChunkAccess.class.getMethod("writeAttachmentsToNBT"), - ChunkAccess.class.getDeclaredMethod("getAttachmentHolder") + ChunkAccess.class.getDeclaredMethod("writeAttachmentsToNBT", HolderLookup.Provider.class), + ChunkAccess.class.getDeclaredMethod("readAttachmentsFromNBT", HolderLookup.Provider.class, CompoundTag.class), + ChunkAccess.class.getDeclaredMethod("getAttachmentHolder"), + ChunkAccess.class.getDeclaredMethod("getLevel") ), Arrays.stream(IAttachmentHolder.class.getMethods())) ); testStaticParity( diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundForgetLevelCloPacket.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundForgetLevelCloPacket.java index 835006cc..76372f50 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundForgetLevelCloPacket.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundForgetLevelCloPacket.java @@ -8,21 +8,19 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import java.util.Optional; - import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.CanBeCubic; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; import io.github.opencubicchunks.cubicchunks.network.CCClientboundForgetLevelCloPacket; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import io.github.opencubicchunks.cubicchunks.testutils.Misc; import io.netty.buffer.Unpooled; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.ChunkPos; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -31,18 +29,19 @@ public class TestCCClientboundForgetLevelCloPacket extends BaseTest { public void serdeTest() { var packet = new CCClientboundForgetLevelCloPacket(CloPos.chunk(3, -1)); var buf = new FriendlyByteBuf(Unpooled.buffer()); - packet.write(buf); - assertDeepEquals(new CCClientboundForgetLevelCloPacket(buf), packet); + CCClientboundForgetLevelCloPacket.STREAM_CODEC.encode(buf, packet); + assertDeepEquals(CCClientboundForgetLevelCloPacket.STREAM_CODEC.decode(buf), packet); packet = new CCClientboundForgetLevelCloPacket(CloPos.cube(5, -3, -4)); buf = new FriendlyByteBuf(Unpooled.buffer()); - packet.write(buf); - assertDeepEquals(new CCClientboundForgetLevelCloPacket(buf), packet); + CCClientboundForgetLevelCloPacket.STREAM_CODEC.encode(buf, packet); + assertDeepEquals(CCClientboundForgetLevelCloPacket.STREAM_CODEC.decode(buf), packet); } + @Disabled // TODO disabled until we can apply client-side mixins in tests properly @Test public void handlerTest() { - PlayPayloadContext payloadContextMock = mock(); + IPayloadContext payloadContextMock = mock(Mockito.RETURNS_DEEP_STUBS); ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS); ClientChunkCache clientChunkCacheMock = mock(ClientChunkCache.class); when(clientLevelMock.getChunkSource()).thenReturn(clientChunkCacheMock); @@ -50,9 +49,7 @@ public void handlerTest() { when(clientLevelMock.getHeight()).thenReturn(384); when(clientLevelMock.getSectionsCount()).thenReturn(24); - when(payloadContextMock.level()).thenReturn(Optional.of(clientLevelMock)); - - when(payloadContextMock.workHandler()).thenReturn(new Misc.DummyWorkHandler()); + when(payloadContextMock.player().level()).thenReturn(clientLevelMock); var cubePos = CubePos.of(0, -22, 41); var chunkPos = new ChunkPos(-999, 1); diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelChunkPacket.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelChunkPacket.java index e271b49a..41b4ebe2 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelChunkPacket.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelChunkPacket.java @@ -10,12 +10,11 @@ import org.junit.jupiter.api.Test; public class TestCCClientboundLevelChunkPacket extends BaseTest { - @Test public void serdeTest() { var packet = new CCClientboundLevelChunkPacket(new ChunkPos(2, 4)); var buf = new FriendlyByteBuf(Unpooled.buffer()); - packet.write(buf); - assertDeepEquals(new CCClientboundLevelChunkPacket(buf), packet); + CCClientboundLevelChunkPacket.STREAM_CODEC.encode(buf, packet); + assertDeepEquals(CCClientboundLevelChunkPacket.STREAM_CODEC.decode(buf), packet); } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelCubeWithLightPacket.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelCubeWithLightPacket.java index c0731d3e..58327dc4 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelCubeWithLightPacket.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundLevelCubeWithLightPacket.java @@ -9,24 +9,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Optional; import java.util.Random; import io.github.opencubicchunks.cc_core.api.CubePos; -import io.github.opencubicchunks.cc_core.utils.Coords; import io.github.opencubicchunks.cubicchunks.CanBeCubic; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; -import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelChunkPacket; import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelCubeWithLightPacket; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import io.github.opencubicchunks.cubicchunks.testutils.Misc; import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; import io.netty.buffer.Unpooled; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.world.level.ChunkPos; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -49,17 +45,18 @@ public void serdeTest() { var buf1 = new FriendlyByteBuf(Unpooled.buffer()); var buf2 = new FriendlyByteBuf(Unpooled.buffer()); - packet1.write(buf1); - packet2.write(buf2); + CCClientboundLevelCubeWithLightPacket.STREAM_CODEC.encode(buf1, packet1); + CCClientboundLevelCubeWithLightPacket.STREAM_CODEC.encode(buf2, packet2); - assertDeepEquals(new CCClientboundLevelCubeWithLightPacket(buf1), packet1); - assertDeepEquals(new CCClientboundLevelCubeWithLightPacket(buf2), packet2); + assertDeepEquals(CCClientboundLevelCubeWithLightPacket.STREAM_CODEC.decode(buf1), packet1); + assertDeepEquals(CCClientboundLevelCubeWithLightPacket.STREAM_CODEC.decode(buf2), packet2); } + @Disabled // TODO disabled until we can apply client-side mixins in tests properly @Test public void handlerTest() { - PlayPayloadContext payloadContextMock = mock(); + IPayloadContext payloadContextMock = mock(Mockito.RETURNS_DEEP_STUBS); ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS); ClientChunkCache clientChunkCacheMock = mock(ClientChunkCache.class); when(clientLevelMock.getChunkSource()).thenReturn(clientChunkCacheMock); @@ -67,9 +64,7 @@ public void handlerTest() { when(clientLevelMock.getHeight()).thenReturn(384); when(clientLevelMock.getSectionsCount()).thenReturn(24); - when(payloadContextMock.level()).thenReturn(Optional.of(clientLevelMock)); - - when(payloadContextMock.workHandler()).thenReturn(new Misc.DummyWorkHandler()); + when(payloadContextMock.player().level()).thenReturn(clientLevelMock); var pos = CubePos.of(10, -2, 4); var cube = generateRandomLevelCube(clientLevelMock, pos, new Random(3333)); diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundSetCubeCacheCenterPacket.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundSetCubeCacheCenterPacket.java index 5e73f256..2c556efc 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundSetCubeCacheCenterPacket.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/network/TestCCClientboundSetCubeCacheCenterPacket.java @@ -8,19 +8,17 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import java.util.Optional; - import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cubicchunks.CanBeCubic; import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache; import io.github.opencubicchunks.cubicchunks.network.CCClientboundSetCubeCacheCenterPacket; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import io.github.opencubicchunks.cubicchunks.testutils.Misc; import io.netty.buffer.Unpooled; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.network.FriendlyByteBuf; -import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -29,13 +27,14 @@ public class TestCCClientboundSetCubeCacheCenterPacket extends BaseTest { public void serdeTest() { var packet = new CCClientboundSetCubeCacheCenterPacket(CubePos.of(-5, 17, 0)); var buf = new FriendlyByteBuf(Unpooled.buffer()); - packet.write(buf); - assertDeepEquals(new CCClientboundSetCubeCacheCenterPacket(buf), packet); + CCClientboundSetCubeCacheCenterPacket.STREAM_CODEC.encode(buf, packet); + assertDeepEquals(CCClientboundSetCubeCacheCenterPacket.STREAM_CODEC.decode(buf), packet); } + @Disabled // TODO disabled until we can apply client-side mixins in tests properly @Test public void handlerTest() { - PlayPayloadContext payloadContextMock = mock(); + IPayloadContext payloadContextMock = mock(Mockito.RETURNS_DEEP_STUBS); ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS); ClientChunkCache clientChunkCacheMock = mock(ClientChunkCache.class); when(clientLevelMock.getChunkSource()).thenReturn(clientChunkCacheMock); @@ -43,10 +42,7 @@ public void handlerTest() { when(clientLevelMock.getHeight()).thenReturn(384); when(clientLevelMock.getSectionsCount()).thenReturn(24); - when(payloadContextMock.level()).thenReturn(Optional.of(clientLevelMock)); - - when(payloadContextMock.workHandler()).thenReturn(new Misc.DummyWorkHandler()); - + when(payloadContextMock.player().level()).thenReturn(clientLevelMock); var cubePos = CubePos.of(3, -2222, 42); var packet = new CCClientboundSetCubeCacheCenterPacket(cubePos); diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java index de014528..eae67144 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java @@ -29,7 +29,7 @@ public class TestMinecraftServer extends BaseTest { private CloseableReference setupServer() { WorldStem worldStemMock = mock(WorldStem.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); - when(worldStemMock.registries().compositeAccess().registryOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)).thenReturn(true); + when(worldStemMock.registries().compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)).thenReturn(true); MockedConstruction serverFunctionManagerMockedConstruction = Mockito.mockConstruction(ServerFunctionManager.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); return new CloseableReference<>( new IntegratedServer(mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), worldStemMock, mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS)), diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloCollectorFuture.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloCollectorFuture.java deleted file mode 100644 index 1ba5d6c8..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloCollectorFuture.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.test.server.level; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; - -import com.mojang.datafixers.util.Either; -import io.github.opencubicchunks.cubicchunks.server.level.CloCollectorFuture; -import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestCloCollectorFuture extends BaseTest { - @Test public void testFutureCompletion() throws Exception { - for (int i = 0; i < 9; i++) { // Run nine times, putting the center cube at each index - var future = new CloCollectorFuture(9); - CloAccess centerCube = mock(); - CloAccess others = mock(); - for (int j = 0; j < 9; j++) { - assertFalse(future.isDone(), "future shouldn't complete until all Clos are added"); - future.add(Either.left(j == i ? centerCube : others), null, j == i); - } - assertTrue(future.isDone(), "future should be complete once all Clos are added"); - assertSame(future.get().get(4).left().get(), centerCube, "centerCube should be at center index"); - for (int j = 0; j < 9; j++) { - if (j == 4) continue; - assertNotSame(future.get().get(j).left().get(), centerCube, "reference to centerCube should not be duplicated at any other index"); - } - } - } - - @Test public void testMultipleCenterCubes() { - var future = new CloCollectorFuture(9); - CloAccess cloAccess = mock(); - future.add(Either.left(cloAccess), null, true); - assertThrows(IllegalStateException.class, () -> future.add(Either.left(cloAccess), null, true), "trying to set centerCube multiple times should throw an exception"); - } - - @Test public void testMissingCenterCube() { - var future = new CloCollectorFuture(9); - CloAccess cloAccess = mock(); - for (int i = 0; i < 8; i++) { - future.add(Either.left(cloAccess), null, false); - } - assertThrows(IllegalStateException.class, () -> future.add(Either.left(cloAccess), null, false), "completing without having set centerCube should throw an exception"); - } - - @Test public void testFutureExceptionPropagation() { - for (int i = 0; i < 9; i++) { // Run nine times, throwing an exception at each index - var future = new CloCollectorFuture(9); - var exception = new Exception("test exception"); - CloAccess cloAccess = mock(); - for (int j = 0; j < 9; j++) { - future.add(Either.left(cloAccess), i == j ? exception : null, false); - } - assertTrue(future.isCompletedExceptionally(), "future should complete exceptionally upon receiving an Exception"); - } - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloGenerationTask.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloGenerationTask.java new file mode 100644 index 00000000..19fb9740 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloGenerationTask.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.server.level; + +import io.github.opencubicchunks.cubicchunks.integrationtest.server.level.IntegrationTestCubicChunkMap; +import net.minecraft.server.level.ChunkGenerationTask; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; + +/** + * We do not unit test {@link ChunkGenerationTask} as it is very tightly coupled with {@link ChunkHolder}, {@link ChunkMap} and {@link ServerChunkCache}. + * + * @see IntegrationTestCubicChunkMap integration tests + */ +public class TestCloGenerationTask { +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloHolder.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloHolder.java index 1f89a0d4..bd8731dc 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloHolder.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloHolder.java @@ -1,12 +1,14 @@ package io.github.opencubicchunks.cubicchunks.test.server.level; import io.github.opencubicchunks.cubicchunks.integrationtest.server.level.IntegrationTestCubicChunkMap; +import net.minecraft.server.level.ChunkGenerationTask; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.server.level.ServerChunkCache; /** - * We do not unit test {@link ChunkHolder} as it is very tightly coupled with {@link ChunkMap} and {@link ServerChunkCache}. + * We do not unit test {@link GenerationChunkHolder} or {@link ChunkHolder} as it is very tightly coupled with {@link ChunkGenerationTask}, {@link ChunkMap} and {@link ServerChunkCache}. * * @see IntegrationTestCubicChunkMap integration tests */ diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloTaskPriorityQueueSorter.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloTaskPriorityQueueSorter.java deleted file mode 100644 index 9e47e3dd..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCloTaskPriorityQueueSorter.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.test.server.level; - -import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -/** - * Tests for {@link io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level.MixinChunkTaskPriorityQueueSorter}. - */ -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestCloTaskPriorityQueueSorter extends BaseTest { - @Disabled("This test is empty, since it is a mixin that is only affecting a debug method. We could test in the future if needed.") - @Test public void testGetDebugStatus() {} -} \ No newline at end of file diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java index eaeda237..e1ea7595 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java @@ -1,12 +1,13 @@ package io.github.opencubicchunks.cubicchunks.test.server.level; import io.github.opencubicchunks.cubicchunks.integrationtest.server.level.IntegrationTestCubicChunkMap; +import net.minecraft.server.level.ChunkGenerationTask; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerChunkCache; /** - * We do not unit test {@link ChunkMap} as it is very tightly coupled with {@link ChunkHolder} and {@link ServerChunkCache}. + * We do not unit test {@link ChunkMap} as it is very tightly coupled with {@link ChunkGenerationTask}, {@link ChunkHolder} and {@link ServerChunkCache}. * * @see IntegrationTestCubicChunkMap integration tests */ diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicClientLevel.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicClientLevel.java index 5bfdb469..440f5696 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicClientLevel.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicClientLevel.java @@ -5,7 +5,7 @@ /** - * This test class is for testing {@link io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer.MixinClientLevel} + * This test class is for testing {@link io.github.opencubicchunks.cubicchunks.mixin.core.client.multiplayer.MixinClientLevel} */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestCubicClientLevel extends BaseTest { diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicDistanceManager.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicDistanceManager.java index 0bcb0ff3..70c64244 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicDistanceManager.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicDistanceManager.java @@ -1,5 +1,6 @@ package io.github.opencubicchunks.cubicchunks.test.server.level; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; @@ -13,29 +14,31 @@ import io.github.opencubicchunks.cc_core.world.level.CloPos; import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; +import io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level.MixinDistanceManager; +import io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level.MixinPlayerTicketTracker; import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.CubicDistanceManagerTestAccess; -import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; +import io.github.opencubicchunks.cubicchunks.world.level.CubicTicketStorage; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.DistanceManager; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; -import net.minecraft.util.Unit; +import net.minecraft.util.TriState; import net.minecraft.util.thread.BlockableEventLoop; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.TicketStorage; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; /** - * Tests for {@link io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level.MixinDistanceManager} and {@link io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level.MixinPlayerTicketTracker} + * Tests for {@link MixinDistanceManager} and {@link MixinPlayerTicketTracker} */ -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestCubicDistanceManager extends BaseTest { static class TestDistanceManager extends DistanceManager { - public TestDistanceManager(Executor executor, Executor executor2) { - super(executor, executor2); + public TestDistanceManager(TicketStorage ticketStorage, Executor executor, Executor executor2) { + super(ticketStorage, executor, executor2); } @Override @@ -61,12 +64,12 @@ static class ServerPlayerAndPosition { } } - private DistanceManager setupDistanceManager() { + private DistanceManager setupDistanceManager(TicketStorage ticketStorage) { var mainThread = Thread.currentThread(); var mainThreadExecutor = // Based on ServerChunkCache.MainThreadExecutor new BlockableEventLoop<>("test_event_loop") { @Override - protected Runnable wrapRunnable(Runnable runnable) { + public Runnable wrapRunnable(Runnable runnable) { return runnable; } @@ -87,21 +90,23 @@ protected Thread getRunningThread() { }; // We run everything on the main thread as Mockito has race conditions when multiple threads call into it // (which occurs when using RETURNS_DEEP_STUBS) - DistanceManager distanceManager = new TestDistanceManager(Runnable::run, mainThreadExecutor); + DistanceManager distanceManager = new TestDistanceManager(ticketStorage, Runnable::run, mainThreadExecutor); return distanceManager; } @Test public void testUpdateChunkForcedVanilla() { - DistanceManager distanceManager = setupDistanceManager(); - ((CubicDistanceManagerTestAccess) distanceManager).invoke_updateChunkForced(ChunkPos.ZERO, true); + var ticketStorage = new TicketStorage(); + DistanceManager distanceManager = setupDistanceManager(ticketStorage); + ticketStorage.updateChunkForced(ChunkPos.ZERO, true); distanceManager.runAllUpdates(mock(ChunkMap.class)); assertTrue(distanceManager.inEntityTickingRange(ChunkPos.ZERO.toLong()), "ChunkPos.ZERO is not in entity ticking range"); } @Test public void testUpdateChunkForced() { - DistanceManager distanceManager = setupDistanceManager(); + var ticketStorage = new TicketStorage(); + DistanceManager distanceManager = setupDistanceManager(ticketStorage); ((MarkableAsCubic) distanceManager).cc_setCubic(); - ((CubicDistanceManagerTestAccess)distanceManager).invoke_updateCubeForced(CloPos.cube(0, 0, 0), true); + ((CubicTicketStorage) ticketStorage).cc_updateChunkForced(CloPos.cube(0, 0, 0), true); distanceManager.runAllUpdates(mock(ChunkMap.class)); assertTrue(distanceManager.inEntityTickingRange(CloPos.cube(0, 0, 0).toLong()), "CloPos.ZERO is not in entity ticking range"); } @@ -117,7 +122,7 @@ private void addAndTestPlayersVanilla(DistanceManager distanceManager, List serverLevelReference = setupServerLevel()) { - assertFalse(serverLevelReference.value().isNaturalSpawningAllowed(new ChunkPos(0, 0))); + assertFalse(serverLevelReference.value().areEntitiesActuallyLoadedAndTicking(new ChunkPos(0, 0))); } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicTickingTracker.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicTickingTracker.java deleted file mode 100644 index 0bc4a2ee..00000000 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicTickingTracker.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.test.server.level; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import io.github.opencubicchunks.cc_core.world.level.CloPos; -import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; -import io.github.opencubicchunks.cubicchunks.server.level.CubicTickingTracker; -import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import net.minecraft.server.level.TickingTracker; -import net.minecraft.world.level.ChunkPos; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -/** - * This test class is for testing {@link TickingTracker} with {@link io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos} instead of {@link ChunkPos}. - *

- * We only test replacePlayerTicketsLevel since that is the only method that needs any bespoke functionality in {@link TickingTracker}. - */ -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestCubicTickingTracker extends BaseTest { - private TickingTracker setupTracker() { - var tracker = new TickingTracker(); - ((MarkableAsCubic) tracker).cc_setCubic(); - return tracker; - } - - @Test public void testReplaceSinglePlayerTicketLevel() { - var tracker = setupTracker(); - var clopos = CloPos.cube(0, 0, 0); - ((CubicTickingTracker)tracker).cc_addTicket(CubicTicketType.PLAYER, clopos, 0, clopos); - tracker.runAllUpdates(); - assertEquals(0, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Adding ticket failed."); - tracker.replacePlayerTicketsLevel(2); - tracker.runAllUpdates(); - assertEquals(2, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Replacing ticket failed."); - } - - @Test public void testReplaceMultiplePlayerTicketsLevel() { - var tracker = setupTracker(); - var clopos = CloPos.cube(0, 0, 0); - ((CubicTickingTracker)tracker).cc_addTicket(CubicTicketType.PLAYER, clopos, 0, clopos); - tracker.runAllUpdates(); - assertEquals(0, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Adding ticket failed."); - ((CubicTickingTracker)tracker).cc_addTicket(CubicTicketType.PLAYER, clopos, 0, clopos); - tracker.runAllUpdates(); - assertEquals(0, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Adding ticket failed."); - tracker.replacePlayerTicketsLevel(2); - tracker.runAllUpdates(); - assertEquals(2, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Replacing ticket failed."); - } - - @Test public void testReplaceSingleNonPlayerTicketLevel() { - var tracker = setupTracker(); - var clopos = CloPos.cube(0, 0, 0); - ((CubicTickingTracker)tracker).cc_addTicket(CubicTicketType.UNKNOWN, clopos, 0, clopos); - tracker.runAllUpdates(); - assertEquals(0, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Adding ticket failed."); - tracker.replacePlayerTicketsLevel(2); - tracker.runAllUpdates(); - assertEquals(0, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Replacing ticket failed."); - } - - @Test public void testReplaceMixedTicketsLevel() { - var tracker = setupTracker(); - var clopos = CloPos.cube(0, 0, 0); - ((CubicTickingTracker)tracker).cc_addTicket(CubicTicketType.PLAYER, clopos, 4, clopos); - tracker.runAllUpdates(); - assertEquals(4, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Adding ticket failed."); - ((CubicTickingTracker)tracker).cc_addTicket(CubicTicketType.UNKNOWN, clopos, 3, clopos); - tracker.runAllUpdates(); - assertEquals(3, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Adding ticket failed."); - tracker.replacePlayerTicketsLevel(2); - tracker.runAllUpdates(); - assertEquals(2, ((CubicTickingTracker)tracker).cc_getLevel(clopos), "Replacing ticket failed."); - } -} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestServerPlayer.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestServerPlayer.java index 881a6e29..5773e2f3 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestServerPlayer.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestServerPlayer.java @@ -1,18 +1,11 @@ package io.github.opencubicchunks.cubicchunks.test.server.level; -import static io.github.opencubicchunks.cubicchunks.testutils.Misc.setupServerLevel; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; -import io.github.opencubicchunks.cubicchunks.testutils.CloseableReference; -import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.level.TicketType; -import net.minecraft.world.level.ChunkPos; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.mockito.Mockito; @@ -26,21 +19,4 @@ private ServerPlayer setupServerPlayer(ServerLevel serverLevel) { serverPlayer.connection = Mockito.mock(); return serverPlayer; } - - @Test public void testTeleportToVanilla() throws Exception { - try (CloseableReference serverLevelReference = setupServerLevel()) { - ServerPlayer player = setupServerPlayer(serverLevelReference.value()); - - // teleportTo takes the destination level to teleport to as a param, so we mock it - ServerLevel serverLevelSpy = spy(serverLevelReference.value()); - ServerChunkCache serverChunkCacheMock = mock(ServerChunkCache.class); - Mockito.when(serverLevelSpy.getChunkSource()).thenReturn(serverChunkCacheMock); - - player.teleportTo(serverLevelSpy, 0, 0, 0, mock(), 0, 0); - - // Verify that serverChunkCacheMock.addRegionTicket(...) is called once - Mockito.verify(serverChunkCacheMock, Mockito.times(1)) - .addRegionTicket(TicketType.POST_TELEPORT, new ChunkPos(0, 0), 1, player.getId()); - } - } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/entity/TestEntity.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/entity/TestEntity.java index 49c50470..02a3efc2 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/entity/TestEntity.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/entity/TestEntity.java @@ -16,6 +16,7 @@ import io.github.opencubicchunks.cubicchunks.world.level.CubicLevelReader; import it.unimi.dsi.fastutil.longs.LongAVLTreeSet; import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.level.Level; @@ -30,7 +31,7 @@ public class TestEntity extends BaseTest { when(((CanBeCubic) level).cc_isCubic()).thenReturn(true); when(level.enabledFeatures()).thenReturn(FeatureFlags.DEFAULT_FLAGS); // We use a giant for testing because it's funny - var entity = EntityType.GIANT.create(level); + var entity = EntityType.GIANT.create(level, EntitySpawnReason.MOB_SUMMONED); var random = new Random(742); for (int i = 0; i < 1000; i++) { var pos = new BlockPos(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); @@ -39,7 +40,7 @@ public class TestEntity extends BaseTest { } } - // TODO (P2) test teleportToWithTicket + // TODO (P2) test teleport methods // Not really a unit test since it depends on CubicLevelReader, but touchingUnloadedChunk is essentially just a wrapper around LevelReader.hasChunksAt anyway @Test public void testTouchingUnloadedChunk() { @@ -59,7 +60,7 @@ public class TestEntity extends BaseTest { when(((CanBeCubic) level).cc_isCubic()).thenReturn(true); when(level.enabledFeatures()).thenReturn(FeatureFlags.DEFAULT_FLAGS); when(((CubicLevelReader) level).cc_hasCubesAt(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt())).then(AdditionalAnswers.delegatesTo(levelReader)); - var entity = EntityType.GIANT.create(level); + var entity = EntityType.GIANT.create(level, EntitySpawnReason.MOB_SUMMONED); for (int i = 0; i < 500; i++) { var cubePos = CubePos.of(random.nextInt(10)-5, random.nextInt(10)-5, random.nextInt(10)-5); // Horizontal center of cube, at bottom diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestBlockCollisions.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestBlockCollisions.java index 6c4a30ee..f25a9bc0 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestBlockCollisions.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestBlockCollisions.java @@ -14,6 +14,7 @@ import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; import io.github.opencubicchunks.cubicchunks.world.level.CubicLevel; import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.BlockCollisions; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; @@ -62,7 +63,7 @@ static BlockGetter mockBlockGetter(BlockState state) { // We don't generate positions on the edge of the cube here since BlockCollisions tries to reach into neighboring cubes in that case var blockPos = cubePos.asBlockPos(random.nextInt(1, CubicConstants.DIAMETER_IN_BLOCKS-1), random.nextInt(1, CubicConstants.DIAMETER_IN_BLOCKS-1), random.nextInt(1, CubicConstants.DIAMETER_IN_BLOCKS-1)); int[] c = new int[] { 0 }; - var blockCollisions = new BlockCollisions(level, null, AABB.unitCubeFromLowerCorner(new Vec3(blockPos.getX(), blockPos.getY(), blockPos.getZ())).inflate(-0.2), false, (pos, voxelShape) -> { + var blockCollisions = new BlockCollisions(level, (Entity) null, AABB.unitCubeFromLowerCorner(new Vec3(blockPos.getX(), blockPos.getY(), blockPos.getZ())).inflate(-0.2), false, (pos, voxelShape) -> { assertEquals(blockPos, pos); c[0]++; return null; @@ -88,7 +89,7 @@ static BlockGetter mockBlockGetter(BlockState state) { // AABB that reaches across the corner between 8 cubes var aabb = AABB.unitCubeFromLowerCorner(new Vec3(blockPos.getX()+0.5, blockPos.getY()+0.5, blockPos.getZ()+0.5)).inflate(-0.2); int[] c = new int[] { 0 }; - var blockCollisions = new BlockCollisions(level, null, aabb, false, (pos, voxelShape) -> { + var blockCollisions = new BlockCollisions(level, (Entity) null, aabb, false, (pos, voxelShape) -> { var collidedCubePos = CubePos.from(pos); System.out.println(pos + " " + collidedCubePos); assertEquals(1, ((collidedCubePos.getX() + collidedCubePos.getY() + collidedCubePos.getZ()) & 1), "should only collide with positions in solid cubes"); diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevel.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevel.java index 82c2f1a8..2d49046e 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevel.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevel.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.withSettings; import java.nio.file.Files; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -24,35 +25,43 @@ import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; +import net.minecraft.core.particles.ParticleOptions; import net.minecraft.resources.ResourceKey; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.TickRateManager; +import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.alchemy.PotionBrewing; +import net.minecraft.world.item.crafting.RecipeAccess; import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.ExplosionDamageCalculator; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.FuelValues; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkSource; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.entity.LevelEntityGetter; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.WritableLevelData; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.Scoreboard; import net.minecraft.world.ticks.LevelTickAccess; +import net.neoforged.neoforge.entity.PartEntity; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -73,61 +82,60 @@ public static class TestLevel extends Level { ChunkSource mockChunkSource = mock(ChunkSource.class, RETURNS_DEEP_STUBS); public TestLevel( - WritableLevelData p_270739_, - ResourceKey p_270683_, - RegistryAccess p_270200_, - Holder p_270240_, - Supplier p_270692_, - boolean p_270904_, - boolean p_270470_, - long p_270248_, - int p_270466_ + WritableLevelData levelData, + ResourceKey dimension, + RegistryAccess registryAccess, + Holder dimensionTypeRegistration, + boolean isClientSide, + boolean isDebug, + long biomeZoomSeed, + int maxChainedNeighborUpdates ) { - super(p_270739_, p_270683_, p_270200_, p_270240_, p_270692_, p_270904_, p_270470_, p_270248_, p_270466_); + super(levelData, dimension, registryAccess, dimensionTypeRegistration, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates); when(((CubeSource)mockChunkSource).cc_getCube(anyInt(), anyInt(), anyInt(), anyBoolean())).thenReturn(mock(LevelCube.class)); when(((CubeSource)mockChunkSource).cc_getCube(anyInt(), anyInt(), anyInt(), any(), anyBoolean())).thenReturn(mock(LevelCube.class)); } - @Override public void sendBlockUpdated(BlockPos p_46612_, BlockState p_46613_, BlockState p_46614_, int p_46615_) { + @Override public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { } - @Override - public void playSeededSound(@Nullable Player p_262953_, double p_263004_, double p_263398_, double p_263376_, Holder p_263359_, SoundSource p_263020_, float p_263055_, - float p_262914_, long p_262991_) { + @Override public void playSeededSound(@Nullable Entity entity, double x, double y, double z, Holder sound, SoundSource source, float volume, float pitch, long seed) { + + } + + @Override public void playSeededSound(@Nullable Entity entity, Entity sourceEntity, Holder sound, SoundSource source, float volume, float pitch, long seed) { } @Override - public void playSeededSound(@Nullable Player p_220372_, Entity p_220373_, Holder p_263500_, SoundSource p_220375_, float p_220376_, float p_220377_, long p_220378_) { + public void explode(@Nullable Entity source, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator damageCalculator, double x, double y, double z, float radius, + boolean fire, ExplosionInteraction explosionInteraction, ParticleOptions smallExplosionParticles, ParticleOptions largeExplosionParticles, + Holder explosionSound) { } @Override public String gatherChunkSourceStats() { - return null; + return ""; } - @Nullable @Override public Entity getEntity(int p_46492_) { + @Override public @Nullable Entity getEntity(int id) { return null; } - @Override public TickRateManager tickRateManager() { - return null; + @Override public Collection> dragonParts() { + return List.of(); } - @Nullable @Override public MapItemSavedData getMapData(String p_46650_) { + @Override public TickRateManager tickRateManager() { return null; } - @Override public void setMapData(String p_151533_, MapItemSavedData p_151534_) { - - } - - @Override public int getFreeMapId() { - return 0; + @Override public @Nullable MapItemSavedData getMapData(MapId mapId) { + return null; } - @Override public void destroyBlockProgress(int p_46506_, BlockPos p_46507_, int p_46508_) { + @Override public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) { } @@ -135,7 +143,7 @@ public void playSeededSound(@Nullable Player p_220372_, Entity p_220373_, Holder return null; } - @Override public RecipeManager getRecipeManager() { + @Override public RecipeAccess recipeAccess() { return null; } @@ -143,39 +151,67 @@ public void playSeededSound(@Nullable Player p_220372_, Entity p_220373_, Holder return null; } - @Override public LevelTickAccess getBlockTicks() { + @Override public PotionBrewing potionBrewing() { return null; } - @Override public LevelTickAccess getFluidTicks() { + @Override public FuelValues fuelValues() { return null; } + @Override public void setDayTimeFraction(float dayTimeFraction) { + + } + + @Override public float getDayTimeFraction() { + return 0; + } + + @Override public float getDayTimePerTick() { + return 0; + } + + @Override public void setDayTimePerTick(float dayTimePerTick) { + + } + @Override public ChunkSource getChunkSource() { return mockChunkSource; } - @Override public void levelEvent(@Nullable Player p_46771_, int p_46772_, BlockPos p_46773_, int p_46774_) { + @Override public void levelEvent(@Nullable Entity entity, int type, BlockPos pos, int data) { } - @Override public void gameEvent(GameEvent p_220404_, Vec3 p_220405_, GameEvent.Context p_220406_) { + @Override public void gameEvent(Holder gameEvent, Vec3 pos, GameEvent.Context context) { } - @Override public float getShade(Direction p_45522_, boolean p_45523_) { + @Override public List players() { + return List.of(); + } + + @Override public Holder getUncachedNoiseBiome(int x, int y, int z) { + return null; + } + + @Override public int getSeaLevel() { return 0; } - @Override public List players() { + @Override public FeatureFlagSet enabledFeatures() { return null; } - @Override public Holder getUncachedNoiseBiome(int p_204159_, int p_204160_, int p_204161_) { + @Override public float getShade(Direction direction, boolean shade) { + return 0; + } + + @Override public LevelTickAccess getBlockTicks() { return null; } - @Override public FeatureFlagSet enabledFeatures() { + @Override public LevelTickAccess getFluidTicks() { return null; } @@ -188,8 +224,8 @@ public void playSeededSound(@Nullable Player p_220372_, Entity p_220373_, Holder private CloseableReference setupTestLevel() { MockedStatic randomStateMockedStatic = Mockito.mockStatic(RandomState.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); ChunkGenerator noiseBasedChunkGeneratorMock = mock(ChunkGenerator.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); - when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); - when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); + when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[3])); + when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[3])); LevelStem levelStemMock = mock(RETURNS_DEEP_STUBS); when(levelStemMock.type().value().height()).thenReturn(384); LevelStorageSource.LevelStorageAccess levelStorageAccessMock = mock(RETURNS_DEEP_STUBS); @@ -206,7 +242,6 @@ private CloseableReference setupTestLevel() { mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), holderMock, - mock(RETURNS_DEEP_STUBS), false, false, 0, diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevelReader.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevelReader.java index 5ba07de7..35cc8744 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevelReader.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicLevelReader.java @@ -14,7 +14,7 @@ import it.unimi.dsi.fastutil.longs.LongAVLTreeSet; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicTicketStorage.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicTicketStorage.java new file mode 100644 index 00000000..27969ca8 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/TestCubicTicketStorage.java @@ -0,0 +1,8 @@ +package io.github.opencubicchunks.cubicchunks.test.world.level; + +import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; + +public class TestCubicTicketStorage extends BaseTest { + // TODO (P2) tests for save/load + // everything else for this class is trivial dasm transforms that don't need to be unit tested +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java index f4d4e1ca..503e0522 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java @@ -13,7 +13,9 @@ import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; +import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.biome.Biome; @@ -22,7 +24,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.UpgradeData; import net.minecraft.world.level.gameevent.GameEventListenerRegistry; @@ -48,7 +50,7 @@ public CubeAccessTestImpl(CubePos cubePos, UpgradeData upgradeData, return null; } - @Override public @Nullable BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) { + @Override public @Nullable BlockState setBlockState(BlockPos pos, BlockState state, int flags) { int sectionIndex = Coords.blockToIndex(pos); int localX = Coords.blockToSectionLocal(pos.getX()); int localY = Coords.blockToSectionLocal(pos.getY()); @@ -64,7 +66,7 @@ public CubeAccessTestImpl(CubePos cubePos, UpgradeData upgradeData, } - @Override public ChunkStatus getStatus() { + @Override public ChunkStatus getPersistedStatus() { return null; } @@ -72,6 +74,10 @@ public CubeAccessTestImpl(CubePos cubePos, UpgradeData upgradeData, } + @Override public @Nullable CompoundTag getBlockEntityNbtForSaving(BlockPos pos, HolderLookup.Provider provider) { + return null; + } + @Override public TickContainerAccess getBlockTicks() { return null; } @@ -80,7 +86,7 @@ public CubeAccessTestImpl(CubePos cubePos, UpgradeData upgradeData, return null; } - @Override public ChunkAccess.TicksToSave getTicksForSerialization() { + @Override public ChunkAccess.PackedTicks getTicksForSerialization(long todoNameThis) { return null; } @@ -106,14 +112,14 @@ private void findBlocks(Random random) { BlockPos pos = cubePos.asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); if (!visitedPositions.add(pos)) continue; if (random.nextBoolean()) { - cubeAccess.setBlockState(pos, Blocks.STONE.defaultBlockState(), false); + cubeAccess.setBlockState(pos, Blocks.STONE.defaultBlockState()); expectedPositions.add(pos); } else { - cubeAccess.setBlockState(pos, Blocks.DIRT.defaultBlockState(), false); + cubeAccess.setBlockState(pos, Blocks.DIRT.defaultBlockState()); } } Set foundPositions = new HashSet<>(); - cubeAccess.findBlocks((state, pos) -> state == Blocks.STONE.defaultBlockState(), (pos, state) -> foundPositions.add(new BlockPos(pos))); + cubeAccess.findBlocks(state -> state == Blocks.STONE.defaultBlockState(), (pos, state) -> foundPositions.add(new BlockPos(pos))); assertEquals(expectedPositions, foundPositions); } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java index d6b19819..028ab770 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java @@ -44,7 +44,7 @@ private void simpleGetSetBlockState(Random random) { .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); var state = random.nextBoolean() ? Blocks.STONE.defaultBlockState() : Blocks.DIRT.defaultBlockState(); states.put(pos, state); - cube.setBlockState(pos, state, false); + cube.setBlockState(pos, state); } for (var pos : states.keySet()) { @@ -62,7 +62,7 @@ private void fluidState(Random random) { var pos = cubePos .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); positions.add(pos); - cube.setBlockState(pos, state, false); + cube.setBlockState(pos, state); } for (var pos : positions) { @@ -79,15 +79,16 @@ private void methodCallsAndBlockEntities(Random random) { BlockState state1 = spy(Blocks.FURNACE.defaultBlockState()); BlockState state2 = spy(Blocks.STONE.defaultBlockState()); - cube.setBlockState(pos, state1, false); + var prevState1 = cube.setBlockState(pos, state1); + assertEquals(Blocks.AIR.defaultBlockState(), prevState1); verify(state1, times(1)).onPlace(any(), eq(pos), eq(Blocks.AIR.defaultBlockState()), eq(false)); assertNotNull(cube.getBlockEntity(pos)); - cube.setBlockState(pos, state2, false); - verify(state1, times(1)).onRemove(any(), eq(pos), eq(state2), eq(false)); + var prevState2 = cube.setBlockState(pos, state2); + assertEquals(state1, prevState2); + // FIXME need a new way to verify that block entity was removed +// verify(state1, times(1)).onRemove(any(), eq(pos), eq(state2), eq(false)); verify(state2, times(1)).onPlace(any(), eq(pos), eq(state1), eq(false)); - // We don't check block entity is gone, since this requires more complex mocking of the Level, - // and it is handled by BlockState.onRemove, which we check is called } @Test public void testGetSetBlockStateAndFluidState() { diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java index 19d82d70..ad21a1cf 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java @@ -29,8 +29,8 @@ public class TestProtoCube extends BaseTest { private ProtoCube makeProtoCube(CubePos cubePos) { LevelHeightAccessor heightAccessor = mock(Answers.RETURNS_DEEP_STUBS); - when(heightAccessor.getMinBuildHeight()).thenReturn(-(1 << 24)); - when(heightAccessor.getMaxBuildHeight()).thenReturn(1 << 24); + when(heightAccessor.getMinY()).thenReturn(-(1 << 24)); + when(heightAccessor.getMaxY()).thenReturn(1 << 24); when(heightAccessor.getHeight()).thenReturn(1 << 25); when(heightAccessor.isOutsideBuildHeight(any())).thenReturn(false); return new ProtoCube(cubePos, mock(Answers.RETURNS_DEEP_STUBS), heightAccessor, mock(Answers.RETURNS_DEEP_STUBS), mock(Answers.RETURNS_DEEP_STUBS)); @@ -47,7 +47,7 @@ private void simpleGetSetBlockState(Random random) { .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); var state = random.nextBoolean() ? Blocks.STONE.defaultBlockState() : Blocks.DIRT.defaultBlockState(); states.put(pos, state); - cube.setBlockState(pos, state, false); + cube.setBlockState(pos, state); } for (var pos : states.keySet()) { @@ -65,7 +65,7 @@ private void fluidState(Random random) { var pos = cubePos .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); positions.add(pos); - cube.setBlockState(pos, state, false); + cube.setBlockState(pos, state); } for (var pos : positions) { diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/DummyChunkProgressListener.java b/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/DummyChunkProgressListener.java new file mode 100644 index 00000000..8e40367c --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/DummyChunkProgressListener.java @@ -0,0 +1,34 @@ +package io.github.opencubicchunks.cubicchunks.testutils; + +import io.github.opencubicchunks.cc_core.world.level.CloPos; +import io.github.opencubicchunks.cubicchunks.server.level.progress.CloProgressListener; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.jetbrains.annotations.Nullable; + +public class DummyChunkProgressListener implements ChunkProgressListener, CloProgressListener { + @Override public void cc_updateSpawnPos(CloPos center) { + + } + + @Override public void cc_onStatusChange(CloPos chunkPosition, @Nullable ChunkStatus newStatus) { + + } + + @Override public void updateSpawnPos(ChunkPos center) { + + } + + @Override public void onStatusChange(ChunkPos chunkPos, @Nullable ChunkStatus chunkStatus) { + + } + + @Override public void start() { + + } + + @Override public void stop() { + + } +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/Misc.java b/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/Misc.java index 84a62d2f..9f2f1dc6 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/Misc.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/testutils/Misc.java @@ -8,9 +8,9 @@ import static org.mockito.Mockito.withSettings; import java.nio.file.Files; +import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; import javax.annotation.Nullable; @@ -27,7 +27,6 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.storage.LevelStorageSource; -import net.neoforged.neoforge.network.handling.ISynchronizedWorkHandler; import org.mockito.Answers; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -44,8 +43,8 @@ public static int chebyshevDistance(ChunkPos a, ChunkPos b) { public static CloseableReference setupServerLevel() { MockedStatic randomStateMockedStatic = Mockito.mockStatic(RandomState.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); ChunkGenerator noiseBasedChunkGeneratorMock = mock(ChunkGenerator.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); - when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); - when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); + when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); + when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); LevelStem levelStemMock = mock(Mockito.RETURNS_DEEP_STUBS); when(levelStemMock.type().value().height()).thenReturn(384); LevelStorageSource.LevelStorageAccess levelStorageAccessMock = mock(Mockito.RETURNS_DEEP_STUBS); @@ -67,7 +66,7 @@ public static CloseableReference setupServerLevel() { Mockito.mock(RETURNS_DEEP_STUBS), false, 0, - mock(RETURNS_DEEP_STUBS), + List.of(), false, mock(RETURNS_DEEP_STUBS)), randomStateMockedStatic); @@ -99,19 +98,4 @@ public static void assertDeepEquals(@Nullable T actual, @Nullable T expected .usingOverriddenEquals() .isEqualTo(expected); } - - public static class DummyWorkHandler implements ISynchronizedWorkHandler { - @Override public void execute(Runnable task) { - task.run(); - } - - @Override public CompletableFuture submitAsync(Runnable task) { - task.run(); - return CompletableFuture.completedFuture(null); - } - - @Override public CompletableFuture submitAsync(Supplier task) { - return CompletableFuture.completedFuture(task.get()); - } - } } diff --git a/src/test/resources/META-INF/neoforge.mods.toml b/src/test/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..82714393 --- /dev/null +++ b/src/test/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,82 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader="javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. +loaderVersion="${loader_version_range}" #mandatory +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license="${mod_license}" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId="${mod_id}" #mandatory +# The version number of the mod +version="${mod_version}" #mandatory +# A display name for the mod +displayName="${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional +# A file name (in the root of the mod JAR) containing a logo for display +#logoFile="examplemod.png" #optional +# A text field displayed in the mod UI +#credits="" #optional +# A text field displayed in the mod UI +authors="${mod_authors}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description='''${mod_description}''' +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.${mod_id}]] #optional + # the modid of the dependency + modId="neoforge" #mandatory + # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). + # 'required' requires the mod to exist, 'optional' does not + # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning + type="required" #mandatory + # Optional field describing why the dependency is required or why it is incompatible + # reason="..." + # The version range of the dependency + versionRange="${neo_version_range}" #mandatory + # An ordering relationship for the dependency. + # BEFORE - This mod is loaded BEFORE the dependency + # AFTER - This mod is loaded AFTER the dependency + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT, or SERVER + side="BOTH" +# Here's another dependency +[[dependencies.${mod_id}]] + modId="minecraft" + type="required" + # This version range declares a minimum of the current minecraft version up to but not including the next major version + versionRange="${minecraft_version_range}" + ordering="NONE" + side="BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features.${mod_id}] +#openGLVersion="[3.2,)" +[[mixins]] +config="${mod_id}.mixins.core.json" + +[[mixins]] +config="${mod_id}.mixins.access.json" + +[[mixins]] +config="${mod_id}.mixins.test.json" diff --git a/src/test/resources/fabric.mod.json b/src/test/resources/fabric.mod.json deleted file mode 100644 index 1557ce3d..00000000 --- a/src/test/resources/fabric.mod.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "schemaVersion": 1, - "id": "cubicchunks", - "name": "CubicChunks test", - "description": "NeoForge currently does not support JUnit; we use this dummy mod file to apply mixins when running JUnit tests through fabric loader.", - "version": "1.0.0", - "mixins": [ - { - "config": "cubicchunks.mixins.core.json", - "environment": "*" - }, - { - "config": "cubicchunks.mixins.access.json", - "environment": "*" - }, - { - "config": "cubicchunks.mixins.test.json", - "environment": "*" - } - ] -} \ No newline at end of file