diff --git a/.github/workflows/gradleBuild.yml b/.github/workflows/gradleBuild.yml index 510e207b5..61838f8a0 100644 --- a/.github/workflows/gradleBuild.yml +++ b/.github/workflows/gradleBuild.yml @@ -26,7 +26,13 @@ jobs: with: java-version: 17 - name: Build with Gradle - run: ./gradlew build + run: ./gradlew build -x test + - name: Run tests + run: | + ./gradlew test + ./gradlew agreeToMinecraftEula + ./gradlew runIntegrationTests -PdisableNetworkingInIntegrationTest=true + - uses: actions/upload-artifact@v2 with: name: Compiled jars diff --git a/.github/workflows/gradleBuildPR.yml b/.github/workflows/gradleBuildPR.yml index 7890fb3c7..06a962ddc 100644 --- a/.github/workflows/gradleBuildPR.yml +++ b/.github/workflows/gradleBuildPR.yml @@ -22,4 +22,9 @@ jobs: with: java-version: 17 - name: Build with Gradle - run: ./gradlew build \ No newline at end of file + run: ./gradlew build -x test + - name: Run tests + run: | + ./gradlew test + ./gradlew agreeToMinecraftEula + ./gradlew runIntegrationTests -PdisableNetworkingInIntegrationTest=true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 53b547ca5..7ce2dd69c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ build # other eclipse run +runIntegrationTests classes # Files from Forge MDK diff --git a/CubicChunksCore b/CubicChunksCore index 1a02b0818..f087cf00f 160000 --- a/CubicChunksCore +++ b/CubicChunksCore @@ -1 +1 @@ -Subproject commit 1a02b08183a470b4e42e947b96be3c6a186a4796 +Subproject commit f087cf00f7d39e5fec77d0c0c3fa2bb4086de97b diff --git a/Jenkinsfile b/Jenkinsfile index 1beddc306..0f05628d6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -50,6 +50,8 @@ pipeline { stage("Test") { steps { sh "./gradlew test" + sh "./gradlew agreeToMinecraftEula" + sh "./gradlew runIntegrationTests -PdisableNetworkingInIntegrationTest=true" } post { success { diff --git a/build.gradle.kts b/build.gradle.kts index f60df7b61..eb4f8297d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ @file:Suppress("INACCESSIBLE_TYPE", "UnstableApiUsage") import io.github.opencubicchunks.gradle.GeneratePackageInfo +import net.fabricmc.loom.task.RemapJarTask import org.gradle.internal.os.OperatingSystem import java.util.* @@ -23,11 +24,14 @@ plugins { val minecraftVersion: String by project val loaderVersion: String by project val fabricVersion: String by project +val installerVersion: String by project val lwjglVersion: String by project val lwjglNatives: String by project val modId: String by project val debugArtifactTransforms: String by project +val disableNetworkingInIntegrationTest: String? by project + javaHeaders { setAcceptedJars(".*CubicChunksCore.*") setConfig(file("javaHeaders.json")) @@ -46,6 +50,8 @@ generatePackageInfo.apply { doFirst { GeneratePackageInfo.generateFiles(project.sourceSets["main"]) GeneratePackageInfo.generateFiles(project.sourceSets["test"]) + GeneratePackageInfo.generateFiles(project.sourceSets["debug"]) + GeneratePackageInfo.generateFiles(project.sourceSets["integrationTest"]) } } @@ -114,6 +120,15 @@ mixinGen { injectorsDefaultRequire = 1 configurationPlugin = "io.github.opencubicchunks.cubicchunks.mixin.TestMixinConfig" } + + config("integration_test") { + required = true + conformVisibility = true + injectorsDefaultRequire = 1 + sourceSet = "integrationTest" + refmap = "integrationTest-CubicChunks-refmap.json" + packageName = "io.github.opencubicchunks.cubicchunks.test.mixin" + } } group = "io.github.opencubicchunks" // http://maven.apache.org/guides/mini/guide-naming-conventions.html @@ -134,6 +149,9 @@ val debugRuntime: Configuration by configurations.creating { val extraTests: Configuration by configurations.creating val shade: Configuration by configurations.creating +val productionRuntimeServer: Configuration by configurations.creating +val productionRuntimeMods: Configuration by configurations.creating + sourceSets { create("debug") { compileClasspath += debugCompile @@ -143,6 +161,14 @@ sourceSets { runtimeClasspath += configurations.runtimeClasspath.get() runtimeClasspath += sourceSets.main.get().output } + create("integrationTest") { + compileClasspath += configurations.compileClasspath.get() + compileClasspath += configurations.testCompileClasspath.get() + compileClasspath += sourceSets.main.get().output + runtimeClasspath += configurations.runtimeClasspath.get() + runtimeClasspath += configurations.testRuntimeClasspath.get() + runtimeClasspath += sourceSets.main.get().output + } } repositories { @@ -169,6 +195,7 @@ repositories { loom { createRemapConfigurations(sourceSets.test.get()) + createRemapConfigurations(sourceSets["integrationTest"]) accessWidenerPath.set(file("src/main/resources/cubicchunks.accesswidener")) // intermediaryUrl = { "http://localhost:9000/intermediary-20w49a-v2.jar" } @@ -223,6 +250,12 @@ loom { server() vmArgs("-Xmx4G") } + create(" ").apply { + server() + source(project.sourceSets["integrationTest"]) + vmArgs("-Xmx4G", "-Dcubicchunks.test.freezeFailingWorlds=true") + runDir("runIntegrationTests") + } } runConfigs.configureEach { isIdeConfigGenerated = true @@ -309,7 +342,13 @@ dependencies { testImplementation("org.hamcrest:hamcrest-junit:2.0.0.0") testImplementation("org.hamcrest:hamcrest:2.2") -} + + "modIntegrationTestImplementation"(fabricApi.module("fabric-gametest-api-v1", fabricVersion)) + + productionRuntimeServer("net.fabricmc:fabric-installer:${installerVersion}:server") + listOf("fabric-api-base", "fabric-command-api-v1", "fabric-networking-v0", "fabric-lifecycle-events-v1", "fabric-resource-loader-v0").forEach { + productionRuntimeMods(fabricApi.module(it, fabricVersion)) + }} val jar: Jar by tasks jar.apply { @@ -325,6 +364,57 @@ if (project.tasks.findByName("ideaSyncTask") != null) { project.tasks.findByName("ideaSyncTask")!!.dependsOn("CubicChunksCore:assemble") } +val integrationTestJar by tasks.creating(Jar::class) { + from(sourceSets["integrationTest"].output) + destinationDirectory.set(File(project.buildDir, "devlibs")) + archiveClassifier.set("testmod") +} + +val remapIntegrationTestJar by tasks.creating(RemapJarTask::class) { + dependsOn(integrationTestJar) + input.set(integrationTestJar.archiveFile) + archiveClassifier.set("integrationTest") + addNestedDependencies.set(false) +} + +val serverPropertiesJar by tasks.creating(Jar::class) { + val propsFile = file("build/tmp/install.properties") + + doFirst { + propsFile.writeText("fabric-loader-version=${loaderVersion}\ngame-version=${minecraftVersion}") + } + + archiveFileName.set("test-server-properties.jar") + destinationDirectory.set(file("build/tmp")) + from(propsFile) +} + +val agreeToMinecraftEula: Task by tasks.creating { + mkdir("run") + file("run/eula.txt").writeText("eula=true") +} + +val runIntegrationTests by tasks.creating(JavaExec::class) { + dependsOn(tasks["remapJar"], remapIntegrationTestJar, serverPropertiesJar) + classpath(productionRuntimeServer, serverPropertiesJar) + mainClass.set("net.fabricmc.installer.ServerLauncher") + workingDir(file("run")) + + doFirst { + workingDir.mkdirs() + + val mods = productionRuntimeMods.files.joinToString(separator = File.pathSeparator) { it.absolutePath } + + jvmArgs( + "-Dfabric.addMods=${(tasks["remapJar"] as AbstractArchiveTask).archiveFile.get().asFile.absolutePath}${File.pathSeparator}${remapIntegrationTestJar.archiveFile.get() + .asFile.absolutePath}${File.pathSeparator}${mods}", + "-Dcubicchunks.test.disableNetwork=${disableNetworkingInIntegrationTest}" + ) + + args("nogui") + } +} + // unzipping subproject (CubicChunksCore) tests val unzipTests by tasks.creating(Copy::class) { outputs.upToDateWhen { diff --git a/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java b/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java index b512e89f7..bb343f724 100644 --- a/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java +++ b/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java @@ -96,6 +96,15 @@ public static class MixinConfig { private Integer injectorsDefaultRequire; private Boolean conformVisibility; + private String sourceSet; + + public String getSourceSet() { + return sourceSet; + } + + public void setSourceSet(String sourceSet) { + this.sourceSet = sourceSet; + } public Boolean getRequired() { return required; @@ -171,9 +180,6 @@ public void setMixinPriority(Integer mixinPriority) { } void generateFiles(JavaPluginConvention convention) throws IOException { - SourceSet main = convention.getSourceSets().getByName("main"); - Set resourcesSet = main.getResources().getSrcDirs(); - Path resources = resourcesSet.iterator().next().getCanonicalFile().toPath(); for (String name : configs.keySet()) { MixinConfig config = new MixinConfig(); Action configure = configs.get(name); @@ -191,6 +197,10 @@ void generateFiles(JavaPluginConvention convention) throws IOException { } configure.execute(config); + SourceSet main = convention.getSourceSets().getByName(config.sourceSet == null ? "main" : config.sourceSet); + Set resourcesSet = main.getResources().getSrcDirs(); + Path resources = resourcesSet.iterator().next().getCanonicalFile().toPath(); + String fileName = String.format(filePattern, name); try (JsonWriter writer = new JsonWriter(Files.newBufferedWriter(resources.resolve(fileName)))) { @@ -236,7 +246,8 @@ void generateFiles(JavaPluginConvention convention) throws IOException { } private void writeMixins(JavaPluginConvention convention, String name, MixinConfig config, JsonWriter writer) throws IOException { - Set classes = getMixinClasses(config, convention.getSourceSets().getByName("main").getAllJava()); + SourceSet main = convention.getSourceSets().getByName(config.sourceSet == null ? "main": config.sourceSet); + Set classes = getMixinClasses(config, main.getAllJava()); Set commonSet = new HashSet<>(); Set clientSet = new HashSet<>(); @@ -281,7 +292,7 @@ private void writeMixins(JavaPluginConvention convention, String name, MixinConf } private Set getMixinClasses(MixinConfig config, SourceDirectorySet allJava) throws IOException { - System.out.println("GetMixin Classes"); + System.out.println("GetMixin Classes for " + config.packageName + " in " + allJava.getSrcDirs()); Set srcPaths = new HashSet<>(); for (File file : allJava.getSrcDirs()) { Path toPath = file.getCanonicalFile().toPath(); diff --git a/gradle.properties b/gradle.properties index 5471588e0..6b88b10a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ modId=cubicchunks minecraftVersion=1.18.2 yarnVersion=1.18.2+build.1 loaderVersion=0.14.19 +installerVersion=0.11.1 # Dependencies fabricVersion=0.67.1+1.18.2 diff --git a/settings.gradle b/settings.gradle index 51877f5c1..4cc9ebdee 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,4 +13,8 @@ pluginManagement { } } +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' +} + include ':CubicChunksCore' \ No newline at end of file diff --git a/src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java b/src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java new file mode 100644 index 000000000..a979c5a7c --- /dev/null +++ b/src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.debug; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java new file mode 100644 index 000000000..e7d3973a5 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java @@ -0,0 +1,26 @@ +package io.github.opencubicchunks.cubicchunks.test; + +import java.util.Collection; +import java.util.HashSet; + +import io.github.opencubicchunks.cubicchunks.test.tests.PlaceholderTests; + +public class IntegrationTests { + public static final boolean DISABLE_NETWORK = System.getProperty("cubicchunks.test.disableNetwork", "false").equals("true"); + public static final boolean FREEZE_FAILING_WORLDS = System.getProperty("cubicchunks.test.freezeFailingWorlds", "false").equals("true"); + + private static final Collection LIGHTING_TESTS = new HashSet<>(); + static { + PlaceholderTests.register(LIGHTING_TESTS); + } + + public static Collection getLightingTests() { + return LIGHTING_TESTS; + } + + public enum TestState { + PASS, + FAIL, + NONE, + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java new file mode 100644 index 000000000..d03f6b1fa --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java @@ -0,0 +1,10 @@ +package io.github.opencubicchunks.cubicchunks.test; + +public interface LevelTestRunner { + void startTestInLevel(LightingIntegrationTest test); + + boolean testFinished(); + IntegrationTests.TestState testState(); + + LightingIntegrationTest getTest(); +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java new file mode 100644 index 000000000..a0dfdcff4 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java @@ -0,0 +1,131 @@ +package io.github.opencubicchunks.cubicchunks.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeAccess; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkStatus; + +public final class LightingIntegrationTest { + public final String testName; + public final long seed; + + public final Consumer setup; + public final Consumer tick; + public final Consumer teardown; + + private IntegrationTests.TestState state = IntegrationTests.TestState.NONE; + private boolean finished = false; + private BlockPos failurePos = null; + private Throwable throwable = null; + + public LightingIntegrationTest( + String testName, long seed, + Consumer setup, + Consumer tick, + Consumer teardown + ) { + this.testName = testName; + this.seed = seed; + this.setup = setup; + this.tick = tick; + this.teardown = teardown; + } + + public boolean isFinished() { + return this.finished; + } + + public IntegrationTests.TestState getState() { + return this.state; + } + + public Optional getFailurePos() { + return Optional.ofNullable(this.failurePos); + } + + public Optional getThrown() { + return Optional.ofNullable(this.throwable); + } + + @Override + public String toString() { + return "LightingIntegrationTest[" + + "setup=" + setup + ", " + + "test=" + tick + ", " + + "teardown=" + teardown + ']'; + } + + public final class TestContext { + private final ServerLevel level; + + public TestContext(ServerLevel level) { + this.level = level; + } + + public void fail(Throwable t) { + finished = true; + state = IntegrationTests.TestState.FAIL; + throwable = t; + } + + public void pass() { + if (!finished) { + finished = true; + state = IntegrationTests.TestState.PASS; + } + } + + public ServerLevel level() { + return level; + } + + public void setFailurePos(@Nullable BlockPos pos) { + failurePos = pos; + } + + public CompletableFuture getCubeLoadFuturesInVolume(CubePos minPos, CubePos maxPos, ChunkStatus requiredStatus) { + var serverChunkCache = ((CubeLoadUnload) level.getChunkSource()); + List>> futures = new ArrayList<>(); + // for each cube: get the cube future + for (int x = minPos.getX(); x <= maxPos.getX(); x++) { + for (int z = minPos.getZ(); z <= maxPos.getZ(); z++) { + for (int y = minPos.getY(); y <= minPos.getY(); y++) { + futures.add(serverChunkCache.getCubeFuture(CubePos.of(x, y, z), requiredStatus)); + } + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + public CompletableFuture getCubeUnloadFuturesInVolume(CubePos minPos, CubePos maxPos, ChunkStatus requiredStatus) { + var serverChunkCache = ((CubeLoadUnload) level.getChunkSource()); + List> futures = new ArrayList<>(); + // for each cube: get the cube future + for (int x = minPos.getX(); x <= maxPos.getX(); x++) { + for (int z = minPos.getZ(); z <= maxPos.getZ(); z++) { + for (int y = minPos.getY(); y <= minPos.getY(); y++) { + futures.add(serverChunkCache.getCubeUnloadFuture(CubePos.of(x, y, z), requiredStatus)); + } + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + public interface CubeLoadUnload { + CompletableFuture> getCubeFuture(CubePos pos, ChunkStatus requiredStatus); + + CompletableFuture getCubeUnloadFuture(CubePos pos, ChunkStatus requiredStatus); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java new file mode 100644 index 000000000..57ffe85ba --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java @@ -0,0 +1,13 @@ +package io.github.opencubicchunks.cubicchunks.test; + +import java.util.Optional; + +import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; + +public interface ServerTestRunner { + @Nullable Pair> firstErrorLocation(); +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java new file mode 100644 index 000000000..0b3cbe797 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java @@ -0,0 +1,63 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.server.level.CubeMapInternal; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.PlayerMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import org.apache.commons.lang3.mutable.MutableObject; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(value = ChunkMap.class, priority = 2000) +public abstract class MixinChunkMap_SendPlayerAllChunks implements CubeMapInternal { + @Shadow @Final private PlayerMap playerMap; + + @Shadow private volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; + + /** MUST match {@link io.github.opencubicchunks.cubicchunks.mixin.core.common.chunk.MixinChunkMap#visibleCubeMap} **/ + @SuppressWarnings({ "JavadocReference", "unused", "MismatchedQueryAndUpdateOfCollection" }) + private volatile Long2ObjectLinkedOpenHashMap visibleCubeMap; + + @Shadow protected abstract void updateChunkTracking(ServerPlayer player, ChunkPos chunkPos, MutableObject packetCache, boolean wasLoaded, + boolean load); + + /** + * @author NotStirred + * @reason Overwriting CC's overwrite of vanilla logic. Here we just send all chunks + */ + @Overwrite + void updatePlayerStatus(ServerPlayer player, boolean track) { + if (track) { + int xFloor = Coords.getCubeXForEntity(player); + int yFloor = Coords.getCubeYForEntity(player); + int zFloor = Coords.getCubeZForEntity(player); + this.playerMap.addPlayer(CubePos.of(xFloor, yFloor, zFloor).asChunkPos().toLong(), player, true); + this.updatePlayerCubePos(player); //This also sends the vanilla packet, as player#ManagedSectionPos is changed in this method. + } else { + SectionPos managedSectionPos = player.getLastSectionPos(); //Vanilla + CubePos cubePos = CubePos.from(managedSectionPos); + this.playerMap.removePlayer(cubePos.asChunkPos().toLong(), player); + } + + //Vanilla + this.visibleChunkMap.forEach((pos, holder) -> { + ChunkPos chunkPos = new ChunkPos(pos); + this.updateChunkTracking(player, chunkPos, new MutableObject<>(), !track, track); + }); + + //CC + this.visibleCubeMap.forEach((pos, holder) -> { + CubePos cubePos = CubePos.from(pos); + this.updateCubeTracking(player, cubePos, new Object[2], !track, track); + }); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDedicatedServer_DisableNetwork.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDedicatedServer_DisableNetwork.java new file mode 100644 index 000000000..bb5c3e73a --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDedicatedServer_DisableNetwork.java @@ -0,0 +1,28 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import static io.github.opencubicchunks.cubicchunks.test.IntegrationTests.DISABLE_NETWORK; + +import java.io.IOException; +import java.net.InetAddress; + +import io.github.opencubicchunks.cubicchunks.CubicChunks; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.network.ServerConnectionListener; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(DedicatedServer.class) +public class MixinDedicatedServer_DisableNetwork { + @Redirect(method = "initServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConnectionListener;startTcpServerListener(Ljava/net/InetAddress;I)V")) + private void noTcpServer(ServerConnectionListener instance, InetAddress address, int port) throws IOException { + if (DISABLE_NETWORK) { + CubicChunks.LOGGER.warn("*".repeat(30)); + CubicChunks.LOGGER.warn("NETWORKING DISABLED!!"); + CubicChunks.LOGGER.warn("*".repeat(30)); + } else { + instance.startTcpServerListener(address, port); + CubicChunks.LOGGER.info("Networking Enabled"); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java new file mode 100644 index 000000000..4fed18969 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java @@ -0,0 +1,21 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; +import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.Ticket; +import net.minecraft.server.level.TicketType; +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.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(DistanceManager.class) +public abstract class MixinDistanceManager_PreventPlayerTickets { + @Dynamic @Inject(method = { "addTicket(JLnet/minecraft/server/level/Ticket;)V", "addCubeTicket(JLnet/minecraft/server/level/Ticket;)V" }, at = @At("HEAD"), cancellable = true) + private void cancelPlayerTickets(long position, Ticket ticket, CallbackInfo ci) { + if (ticket.getType() == TicketType.PLAYER || ticket.getType() == CubicTicketType.PLAYER) { + ci.cancel(); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java new file mode 100644 index 000000000..ac4b75747 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(LevelRenderer.class) +public class MixinLevelRenderer_NoLightUpdates { + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runUpdates(IZZ)I")) + private int noLightUpdate(LevelLightEngine instance, int i, boolean bl, boolean bl2) { + return i; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java new file mode 100644 index 000000000..3e09faa93 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java @@ -0,0 +1,14 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.Main; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(Main.class) +public class MixinMain { + @Redirect(method = "main", at = @At(value = "INVOKE", target = "Ljava/lang/Runtime;addShutdownHook(Ljava/lang/Thread;)V"), remap = false) + private static void noShutdownHook(Runtime instance, Thread hook) { + // Do nothing + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java new file mode 100644 index 000000000..09ce809c2 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java @@ -0,0 +1,222 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Executor; +import java.util.function.BooleanSupplier; + +import javax.annotation.Nullable; + +import com.google.common.collect.ImmutableList; +import io.github.opencubicchunks.cubicchunks.CubicChunks; +import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; +import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; +import io.github.opencubicchunks.cubicchunks.test.ServerTestRunner; +import io.github.opencubicchunks.cubicchunks.test.util.IndentingStringBuilder; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.DerivedLevelData; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.storage.WorldData; +import org.apache.commons.io.file.PathUtils; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +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; + +@Mixin(value = MinecraftServer.class, priority = 999) +public abstract class MixinMinecraftServer_TestRunner implements ServerTestRunner { + @Shadow @Final protected WorldData worldData; + + @Shadow @Final protected LevelStorageSource.LevelStorageAccess storageSource; + + private boolean isFrozen = false; + + private final Collection incompleteTests = IntegrationTests.getLightingTests(); + private final Collection failedTests = new ArrayList<>(); + + @Nullable private Pair> firstError = null; + + @Shadow @Final private Map, ServerLevel> levels; + + @Shadow @Final private Executor executor; + + @Shadow public abstract RegistryAccess.Frozen registryAccess(); + + @Shadow public abstract void halt(boolean waitForServer); + + @Override @Nullable public Pair> firstErrorLocation() { + return this.firstError; + } + + @Inject(method = "createLevels", at = @At(value = "HEAD"), cancellable = true) + private void createTestLevels(ChunkProgressListener chunkProgressListener, CallbackInfo ci) { + ci.cancel(); + + // each registered test gets its own level + IntegrationTests.getLightingTests().forEach(test -> { + ResourceKey levelResourceKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(test.testName)); + Holder dimensionTypeHolder = this.registryAccess().registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getOrCreateHolder(DimensionType.OVERWORLD_LOCATION); + ChunkGenerator chunkGenerator2 = WorldGenSettings.makeDefaultOverworld(this.registryAccess(), test.seed); + DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, this.worldData.overworldData()); + + Path dimensionPath = this.storageSource.getDimensionPath(levelResourceKey); + try { + if (Files.exists(dimensionPath)) { + PathUtils.deleteDirectory(dimensionPath); + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to delete test dimension directory", e); + } + + ServerLevel level = new ServerLevel( + (MinecraftServer) (Object) this, this.executor, this.storageSource, + derivedLevelData, levelResourceKey, dimensionTypeHolder, chunkProgressListener, + chunkGenerator2, false, test.seed, + ImmutableList.of(), false); + + ((LevelTestRunner) level).startTestInLevel(test); + + this.levels.put(levelResourceKey, level); + }); + + } + + @Inject(method = "prepareLevels", at = @At("HEAD"), cancellable = true) + private void prepareLevels(ChunkProgressListener progressListener, CallbackInfo ci) { + ci.cancel(); + } + + @Inject(method = "tickServer", at = @At("RETURN")) + private void unloadLevelWhenTestFinish(BooleanSupplier hasTimeLeft, CallbackInfo ci) { + for (ResourceKey levelResourceKey : new ArrayList<>(this.levels.keySet())) { + ServerLevel level = this.levels.get(levelResourceKey); + LevelTestRunner levelTestRunner = (LevelTestRunner) level; + if (levelTestRunner.testFinished()) { + LightingIntegrationTest test = levelTestRunner.getTest(); + incompleteTests.remove(test); + + if (test.getState() == IntegrationTests.TestState.FAIL) { + this.failedTests.add(test); + if (firstError == null) { + firstError = new ObjectObjectImmutablePair<>(level, test.getFailurePos()); + } + } + + // Failing test levels are not unloaded if FREEZE_FAILING_WORLDS is set + if (test.getState() != IntegrationTests.TestState.FAIL || !IntegrationTests.FREEZE_FAILING_WORLDS) { + // Unload the test's level + ServerLevel removed = this.levels.remove(levelResourceKey); + try { + System.out.println("Removed " + levelResourceKey); + removed.close(); + } catch (IOException e) { + System.err.println("Exception when closing test level"); + e.printStackTrace(System.err); + } + } + } + } + + if (this.incompleteTests.isEmpty()) { + if (IntegrationTests.FREEZE_FAILING_WORLDS && !this.failedTests.isEmpty()) { + if (!this.isFrozen) { + this.isFrozen = true; + CubicChunks.LOGGER.info("Tests complete! Holding failing worlds open"); + } + } else { + CubicChunks.LOGGER.info("Tests complete! Exiting..."); + this.halt(false); + if (!this.failedTests.isEmpty()) { + CubicChunks.LOGGER.warn(testFailureInformation(this.failedTests)); + System.exit(-1); + } else { + CubicChunks.LOGGER.info("No failed tests!"); + System.exit(0); + } + } + } + } + + private static String testFailureInformation(Collection failedTests) { + IndentingStringBuilder s = new IndentingStringBuilder(4) + .append("Failed Tests: ").append(failedTests.size()).appendNewLine().appendNewLine(); + + for (LightingIntegrationTest test : failedTests) { + s.append("Failure: ").append(test.testName).appendNewLine().indent(); + + Optional thrown = test.getThrown(); + thrown.ifPresent(throwable -> { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + + s.append(sw) + .unIndent().appendNewLine(); + }); + } + + return s.toString(); + } + + /** + * @author NotStirred + * @reason By default, the player is spawned in the overworld. Instead spawn the player in the first error location, or some valid world + * if none exists. + */ + @Overwrite + public final ServerLevel overworld() { + Pair> errorLocation = firstErrorLocation(); + + if (errorLocation != null) { + return errorLocation.first(); + } else { + // if there is no error location, we just place the player in some valid level + return this.levels.values().iterator().next(); + } + } + + @Redirect(method = "saveAllChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/ServerLevelData;" + + "setWorldBorder(Lnet/minecraft/world/level/border/WorldBorder$Settings;)V")) + private void preventNoOverworldNPE(ServerLevelData instance, WorldBorder.Settings settings) { + // do nothing instead + } + @Redirect(method = "saveAllChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;" + + "getWorldBorder()Lnet/minecraft/world/level/border/WorldBorder;")) + private WorldBorder preventNoOverworldNPE(ServerLevel instance) { + return null; + } + @Redirect(method = "saveAllChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/border/WorldBorder;" + + "createSettings()Lnet/minecraft/world/level/border/WorldBorder$Settings;")) + private WorldBorder.Settings preventNoOverworldNPE(WorldBorder instance) { + return null; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java new file mode 100644 index 000000000..229a3e7cc --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.network.Connection; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.level.GameType; +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(PlayerList.class) +public class MixinPlayerLists_SetSpectatorOnJoin { + @Inject(method = "placeNewPlayer", at = @At(value = ("RETURN"))) + private void setSpectator(Connection netManager, ServerPlayer player, CallbackInfo ci) { + player.setGameMode(GameType.SPECTATOR); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java new file mode 100644 index 000000000..cfa18263f --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java @@ -0,0 +1,21 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(PlayerList.class) +public class MixinPlayerLists_TestRenderDistance { + private static final int CLIENT_RENDER_DISTANCE = 64; + + @Redirect(method = "placeNewPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/server/players/PlayerList;viewDistance:I")) + private int testClientRenderDistance(PlayerList instance) { + return CLIENT_RENDER_DISTANCE; + } + + @Redirect(method = "placeNewPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/server/players/PlayerList;simulationDistance:I")) + private int testClientSimulationDistance(PlayerList instance) { + return CLIENT_RENDER_DISTANCE; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java new file mode 100644 index 000000000..7d7c94973 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java @@ -0,0 +1,81 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import java.util.concurrent.CompletableFuture; + +import javax.annotation.Nullable; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.chunk.VerticalViewDistanceListener; +import io.github.opencubicchunks.cubicchunks.mixin.access.common.ChunkMapAccess; +import io.github.opencubicchunks.cubicchunks.server.level.CubeHolder; +import io.github.opencubicchunks.cubicchunks.server.level.CubeMap; +import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; +import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeStatus; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.LightCubeGetter; +import net.minecraft.Util; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +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; + +@Mixin(ServerChunkCache.class) +public abstract class MixinServerChunkCache_CubeLoadUnload implements LightingIntegrationTest.TestContext.CubeLoadUnload, ServerCubeCache, LightCubeGetter, VerticalViewDistanceListener { + @Shadow @Final public ChunkMap chunkMap; + @Shadow @Final ServerLevel level; + @Shadow @Final private DistanceManager distanceManager; + + @Shadow protected abstract boolean chunkAbsent(@Nullable ChunkHolder chunkHolderIn, int i); + + @Override public CompletableFuture> getCubeFuture(CubePos pos, ChunkStatus requiredStatus) { + CubePos cubePos = CubePos.of(pos.getX(), pos.getY(), pos.getZ()); + long i = cubePos.asLong(); + int j = 33 + CubeStatus.getDistance(requiredStatus); + ChunkHolder chunkholder = this.getVisibleCubeIfPresent(i); + ((CubicDistanceManager) this.distanceManager).addCubeTicket(TicketType.START, cubePos, j, Unit.INSTANCE); + if (this.chunkAbsent(chunkholder, j)) { + this.runCubeDistanceManagerUpdates(); + chunkholder = this.getVisibleCubeIfPresent(i); + if (this.chunkAbsent(chunkholder, j)) { + throw Util.pauseInIde(new IllegalStateException("No cube holder after ticket has been added")); + } + } + + if (this.chunkAbsent(chunkholder, j)) { + throw Util.pauseInIde(new IllegalStateException("This shouldn't happen")); + } + + return ((CubeHolder) chunkholder).getOrScheduleCubeFuture(requiredStatus, this.chunkMap); + } + + @Override public CompletableFuture getCubeUnloadFuture(CubePos cubePos, ChunkStatus requiredStatus) { + long i = cubePos.asLong(); + int j = 33 + CubeStatus.getDistance(requiredStatus); + ((CubicDistanceManager) this.distanceManager).removeCubeTicket(TicketType.START, cubePos, j, Unit.INSTANCE); + var cubeHolder = ((CubeHolder) this.getVisibleCubeIfPresent(i)); + return cubeHolder.getCubeToSave(); + } + + // getVisibleChunkIfPresent + @Nullable + private ChunkHolder getVisibleCubeIfPresent(long cubePosIn) { + return ((CubeMap) this.chunkMap).getVisibleCubeIfPresent(cubePosIn); + } + + // runDistanceManagerUpdates + private boolean runCubeDistanceManagerUpdates() { + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = ((ChunkMapAccess) this.chunkMap).invokePromoteChunkMap(); + return flag || flag1; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java new file mode 100644 index 000000000..4f67b08ee --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.level.ServerChunkCache; +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(ServerChunkCache.class) +public class MixinServerChunkCache_NoSave { + @Inject(method = "save", at = @At("HEAD"), cancellable = true) + private void noSave(boolean flush, CallbackInfo ci) { + ci.cancel(); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java new file mode 100644 index 000000000..dcbaf30c5 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerGamePacketListenerImpl.class) +public class MixinServerGamePacketListenerImpl_NoChunkLoad { + /** + * @author NotStirred + * @reason Vanilla impl accesses blocks around the player (and adds tickets) + */ + @Overwrite + private boolean noBlocksAround(Entity entity) { + return true; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java new file mode 100644 index 000000000..2b12d2e8b --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java @@ -0,0 +1,19 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProgressListener; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerLevel.class) +public class MixinServerLevel_NoSave { + /** + * @author NotStirred + * @reason Integration tests shouldn't save worlds + */ + @Overwrite + public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) { + + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java new file mode 100644 index 000000000..1b66a8cad --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java @@ -0,0 +1,78 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import java.util.function.BooleanSupplier; + +import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; +import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; +import net.minecraft.server.level.ServerLevel; +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(ServerLevel.class) +public class MixinServerLevel_TestRunner implements LevelTestRunner { + private boolean testStarted = false; + private LightingIntegrationTest test; + private LightingIntegrationTest.TestContext context; + + private boolean teardownComplete = false; + + @Override public void startTestInLevel(LightingIntegrationTest integrationTest) { + this.test = integrationTest; + this.context = integrationTest.new TestContext((ServerLevel) (Object) this); + this.testStarted = true; + + try { + this.test.setup.accept(this.context); + } catch (Throwable t) { + this.context.fail(t); + } + } + + @Override public boolean testFinished() { + return this.testStarted && test.isFinished(); + } + + @Override public IntegrationTests.TestState testState() { + return test.getState(); + } + + @Override public LightingIntegrationTest getTest() { + return this.test; + } + + @Inject(method = "tick", at = @At("HEAD"), cancellable = true) + private void tickTest(BooleanSupplier b, CallbackInfo ci) { + if (this.test != null && this.test.getState() == IntegrationTests.TestState.FAIL) { + // The test has failed, we don't want to update the level in any way for debugging purposes + if (IntegrationTests.FREEZE_FAILING_WORLDS) { + ci.cancel(); + return; + } + } + + if (this.test != null && !this.test.isFinished()) { + try { + this.test.tick.accept(this.context); + } catch (Throwable t) { + this.context.fail(t); + } + } + } + + @Inject(method = "tick", at = @At("RETURN")) + private void handleTestFinished(BooleanSupplier b, CallbackInfo ci) { + if (this.test != null) { + if (!this.teardownComplete && this.test.isFinished() && this.test.getState() != IntegrationTests.TestState.FAIL) { + this.teardownComplete = true; + try { + this.test.teardown.accept(this.context); + } catch (Throwable t) { + this.context.fail(t); + } + } + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java new file mode 100644 index 000000000..4a460645a --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java @@ -0,0 +1,16 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerPlayer.class) +public class MixinServerPlayer_NoChunkLoad { + /** + * @author NotStirred + * @reason In integration tests we never check this (internally it checks blocks, and loads chunks) + */ + @Overwrite + public void doCheckFallDamage(double y, boolean onGround) { + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java new file mode 100644 index 000000000..5f6f82ab9 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java @@ -0,0 +1,26 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerPlayer.class) +public abstract class MixinServerPlayer_NoTick extends Player { + public MixinServerPlayer_NoTick(Level level, BlockPos blockPos, float f, GameProfile gameProfile) { + super(level, blockPos, f, gameProfile); + } + + /** + * @author NotStirred + * @reason Vanilla ticks players on packet received from the client. We do the minimum possible ticking. + */ + @Overwrite + public void doTick() { + this.noPhysics = true; + this.onGround = false; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java new file mode 100644 index 000000000..873a6109d --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java @@ -0,0 +1,38 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import java.util.Optional; + +import io.github.opencubicchunks.cubicchunks.test.ServerTestRunner; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +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; + +@Mixin(ServerPlayer.class) +public abstract class MixinServerPlayer_SpawnLocationTest extends Entity { + public MixinServerPlayer_SpawnLocationTest(EntityType entityType, Level level) { + super(entityType, level); + } + + @Shadow public abstract void moveTo(double x, double y, double z); + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V")) + private void spawnAtTestLocation(ServerPlayer instance, ServerLevel serverLevel) { + Pair> spawnLocation = ((ServerTestRunner) serverLevel.getServer()).firstErrorLocation(); + + if (spawnLocation.second().isPresent()) { + this.moveTo(spawnLocation.second().get(), 0.0f, 0.0f); + this.setPos(this.getX(), this.getY() + 1.0, this.getZ()); + } else { + this.moveTo(new BlockPos(0, 0, 0), 0.0f, 0.0f); + this.setPos(this.getX(), this.getY() + 1.0, this.getZ()); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/package-info.java new file mode 100644 index 000000000..a0c82dba2 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java new file mode 100644 index 000000000..692ea2191 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java new file mode 100644 index 000000000..19504d12b --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java @@ -0,0 +1,92 @@ +package io.github.opencubicchunks.cubicchunks.test.tests; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.server.level.CubeMap; +import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeStatus; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import net.minecraft.world.level.chunk.ChunkStatus; + +public class PlaceholderTests { + public static void register(Collection lightingTests) { + lightingTests.add(new LightingIntegrationTest("test_0", 0, + context -> { + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).addCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); + System.out.println("Test 0 start"); + }, + context -> { + System.out.println("Test 0 tick"); + context.pass(); + }, + context -> { + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).removeCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); + System.out.println("Test 0 end"); + } + )); + AtomicInteger ticksPassed = new AtomicInteger(); + lightingTests.add(new LightingIntegrationTest("test_1", 0, + context -> { + System.out.println("Test 1 start"); + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).addCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); + }, + context -> { + System.out.println("Test 1 tick"); + if (ticksPassed.getAndIncrement() == 30) { + context.pass(); + } + }, + context -> { + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).removeCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); + System.out.println("Test 1 end"); + } + )); + var minPos = CubePos.of(-1, -1, -1); + var maxPos = CubePos.of(1, 1, 1); + var requiredLevel = ChunkStatus.LIGHT; + var unloading = new boolean[] {false}; + CompletableFuture[] future = new CompletableFuture[1]; + lightingTests.add(new LightingIntegrationTest("test_2", 0, + context -> { + System.out.println("Test 2 start"); + future[0] = context.getCubeLoadFuturesInVolume(minPos, maxPos, requiredLevel); + }, + context -> { + System.out.println("Test 2 tick"); + if (!future[0].isDone()) return; + if (!unloading[0]) { + unloading[0] = true; + future[0] = context.getCubeUnloadFuturesInVolume(minPos, maxPos, requiredLevel); + return; + } + context.pass(); + }, + context -> { + ((CubeMap) context.level().getChunkSource().chunkMap).getUpdatingCubeMap().values().stream().filter(holder -> { + var status = holder.getLastAvailableStatus(); + return status != null && status != ChunkStatus.EMPTY; + }).forEach( + holder -> System.out.println(holder.getLastAvailableStatus()) + ); + System.out.println("Test 2 end"); + } + )); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java new file mode 100644 index 000000000..994acdc96 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test.tests; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java new file mode 100644 index 000000000..9261d8e65 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java @@ -0,0 +1,45 @@ +package io.github.opencubicchunks.cubicchunks.test.util; + +public class IndentingStringBuilder { + private final int indentation; + private int indentationLevel = 0; + private final StringBuilder wrapped = new StringBuilder(); + + public IndentingStringBuilder(int indentation) { + this.indentation = indentation; + } + + public IndentingStringBuilder append(String string) { + String indent = " ".repeat(this.indentation * this.indentationLevel); + for (String line : string.split("\n")) { + this.wrapped.append(indent); + this.wrapped.append(line).append('\n'); + } + // remove last \n + this.wrapped.deleteCharAt(this.wrapped.length() - 1); + return this; + } + + public IndentingStringBuilder append(Object obj) { + return this.append(String.valueOf(obj)); + } + + public IndentingStringBuilder appendNewLine() { + this.wrapped.append('\n'); + return this; + } + + public IndentingStringBuilder indent() { + this.indentationLevel++; + return this; + } + + public IndentingStringBuilder unIndent() { + this.indentationLevel--; + return this; + } + + @Override public String toString() { + return this.wrapped.toString(); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java new file mode 100644 index 000000000..9d98735a2 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test.util; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json new file mode 100644 index 000000000..6bee09174 --- /dev/null +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -0,0 +1,33 @@ +{ + "required": true, + "package": "io.github.opencubicchunks.cubicchunks.test.mixin", + "refmap": "integrationTest-CubicChunks-refmap.json", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "conformVisibility": true + }, + "mixins": [], + "client": [], + "server": [ + "server.MixinChunkMap_SendPlayerAllChunks", + "server.MixinDedicatedServer_DisableNetwork", + "server.MixinDistanceManager_PreventPlayerTickets", + "server.MixinLevelRenderer_NoLightUpdates", + "server.MixinMain", + "server.MixinMinecraftServer_TestRunner", + "server.MixinPlayerLists_SetSpectatorOnJoin", + "server.MixinPlayerLists_TestRenderDistance", + "server.MixinServerChunkCache_CubeLoadUnload", + "server.MixinServerChunkCache_NoSave", + "server.MixinServerGamePacketListenerImpl_NoChunkLoad", + "server.MixinServerLevel_NoSave", + "server.MixinServerLevel_TestRunner", + "server.MixinServerPlayer_NoChunkLoad", + "server.MixinServerPlayer_NoTick", + "server.MixinServerPlayer_SpawnLocationTest" + ] +} \ No newline at end of file diff --git a/src/integrationTest/resources/fabric.mod.json b/src/integrationTest/resources/fabric.mod.json new file mode 100644 index 000000000..7057e0b3c --- /dev/null +++ b/src/integrationTest/resources/fabric.mod.json @@ -0,0 +1,12 @@ +{ + "schemaVersion": 1, + "id": "cubicchunks-integration-tests", + "name": "CubicChunks2 Integration Tests", + "version": "1.0.0", + "environment": "*", + "license": "MIT", + "entrypoints": { }, + "mixins": [ + "cubicchunks.mixins.integration_test.json" + ] +} \ No newline at end of file diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java index 3b01d3f3f..a8b6d0109 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java @@ -176,6 +176,7 @@ public BlockGetter getChunkForLighting(int i, int j) { private CubeTaskPriorityQueueSorter cubeQueueSorter; private final Long2ObjectLinkedOpenHashMap updatingCubeMap = new Long2ObjectLinkedOpenHashMap<>(); + /** MUST match MixinChunkMap_SendPlayerAllChunks#visibleCubeMap in integration tests **/ private volatile Long2ObjectLinkedOpenHashMap visibleCubeMap = this.updatingCubeMap.clone(); // NOTE: used from ASM, don't rename @@ -1267,7 +1268,8 @@ public void setIncomingVerticalViewDistance(int verticalDistance) { } // updatePlayerPos - private SectionPos updatePlayerCubePos(ServerPlayer serverPlayerEntityIn) { + @Override + public SectionPos updatePlayerCubePos(ServerPlayer serverPlayerEntityIn) { SectionPos sectionpos = SectionPos.of(serverPlayerEntityIn); serverPlayerEntityIn.setLastSectionPos(sectionpos); PacketDispatcher.sendTo(new PacketUpdateCubePosition(sectionpos), serverPlayerEntityIn); @@ -1276,7 +1278,8 @@ private SectionPos updatePlayerCubePos(ServerPlayer serverPlayerEntityIn) { } // updateChunkTracking - protected void updateCubeTracking(ServerPlayer player, CubePos cubePosIn, Object[] packetCache, boolean wasLoaded, boolean load) { + @Override + public void updateCubeTracking(ServerPlayer player, CubePos cubePosIn, Object[] packetCache, boolean wasLoaded, boolean load) { if (player.level == this.level) { //TODO: reimplement forge event //net.minecraftforge.event.ForgeEventFactory.fireChunkWatch(wasLoaded, load, player, cubePosIn, this.world); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java index cfd4f8ed4..c34b2ee23 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java @@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.api.CubicConstants; import io.github.opencubicchunks.cc_core.utils.Coords; @@ -51,20 +52,20 @@ public abstract class MixinDistanceManager implements CubicDistanceManager, Vert private boolean isCubic; // fields below used from ASM - final Long2ObjectMap> playersPerCube = new Long2ObjectOpenHashMap<>(); - final Long2ObjectOpenHashMap>> cubeTickets = new Long2ObjectOpenHashMap<>(); - private final CubeTicketTracker cubeTicketTracker = new CubeTicketTracker(this); - - private final FixedPlayerDistanceCubeTracker naturalSpawnCubeCounter = new FixedPlayerDistanceCubeTracker(this, 8 / CubicConstants.DIAMETER_IN_SECTIONS); - private final CubeTickingTracker tickingCubeTicketsTracker = new CubeTickingTracker(); - private final CubicPlayerTicketTracker cubicPlayerTicketManager = new CubicPlayerTicketTracker(this, MathUtil.ceilDiv(33, CubicConstants.DIAMETER_IN_SECTIONS)); - final Set cubesToUpdateFutures = Sets.newHashSet(); - CubeTaskPriorityQueueSorter cubeTicketThrottler; - ProcessorHandle> cubeTicketThrottlerInput; - ProcessorHandle cubeTicketThrottlerReleaser; - final LongSet cubeTicketsToRelease = new LongOpenHashSet(); - - private long cubeTicketTickCounter; + @UsedFromASM final Long2ObjectMap> playersPerCube = new Long2ObjectOpenHashMap<>(); + @UsedFromASM final Long2ObjectOpenHashMap>> cubeTickets = new Long2ObjectOpenHashMap<>(); + @UsedFromASM private final CubeTicketTracker cubeTicketTracker = new CubeTicketTracker(this); + + @UsedFromASM private final FixedPlayerDistanceCubeTracker naturalSpawnCubeCounter = new FixedPlayerDistanceCubeTracker(this, 8 / CubicConstants.DIAMETER_IN_SECTIONS); + @UsedFromASM private final CubeTickingTracker tickingCubeTicketsTracker = new CubeTickingTracker(); + @UsedFromASM private final CubicPlayerTicketTracker cubicPlayerTicketManager = new CubicPlayerTicketTracker(this, MathUtil.ceilDiv(33, CubicConstants.DIAMETER_IN_SECTIONS)); + @UsedFromASM final Set cubesToUpdateFutures = Sets.newHashSet(); + @UsedFromASM CubeTaskPriorityQueueSorter cubeTicketThrottler; + @UsedFromASM ProcessorHandle> cubeTicketThrottlerInput; + @UsedFromASM ProcessorHandle cubeTicketThrottlerReleaser; + @UsedFromASM final LongSet cubeTicketsToRelease = new LongOpenHashSet(); + + @UsedFromASM private long cubeTicketTickCounter; @Shadow abstract void addTicket(long position, Ticket ticket); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java index 34921121f..1af66b7a1 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java @@ -1,8 +1,13 @@ package io.github.opencubicchunks.cubicchunks.server.level; import io.github.opencubicchunks.cc_core.api.CubePos; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ServerPlayer; public interface CubeMapInternal { + SectionPos updatePlayerCubePos(ServerPlayer player); + + void updateCubeTracking(ServerPlayer player, CubePos cubePosIn, Object[] packetCache, boolean wasLoaded, boolean load); boolean isExistingCubeFull(CubePos pos); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java b/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java index f1b8d43d0..65ed50461 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java @@ -34,4 +34,4 @@ public void add(int idx, Either eit this.complete(Arrays.asList(results)); } } -} \ No newline at end of file +}