mirror of https://github.com/Minestom/Minestom.git
fix: always execute chunk load callback/event on the instance tick thread
This commit is contained in:
parent
a31d239b51
commit
6f01aa51ff
|
@ -20,6 +20,7 @@ import net.minestom.server.network.packet.server.play.BlockChangePacket;
|
||||||
import net.minestom.server.network.packet.server.play.BlockEntityDataPacket;
|
import net.minestom.server.network.packet.server.play.BlockEntityDataPacket;
|
||||||
import net.minestom.server.network.packet.server.play.EffectPacket;
|
import net.minestom.server.network.packet.server.play.EffectPacket;
|
||||||
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
|
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
|
||||||
|
import net.minestom.server.thread.TickSchedulerThread;
|
||||||
import net.minestom.server.utils.NamespaceID;
|
import net.minestom.server.utils.NamespaceID;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
|
@ -44,6 +45,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static net.minestom.server.utils.chunk.ChunkUtils.*;
|
import static net.minestom.server.utils.chunk.ChunkUtils.*;
|
||||||
|
@ -291,6 +293,15 @@ public class InstanceContainer extends Instance {
|
||||||
final CompletableFuture<Chunk> prev = loadingChunks.putIfAbsent(index, completableFuture);
|
final CompletableFuture<Chunk> prev = loadingChunks.putIfAbsent(index, completableFuture);
|
||||||
if (prev != null) return prev;
|
if (prev != null) return prev;
|
||||||
final IChunkLoader loader = chunkLoader;
|
final IChunkLoader loader = chunkLoader;
|
||||||
|
final Consumer<Chunk> postLoadCallback = chunk -> {
|
||||||
|
cacheChunk(chunk);
|
||||||
|
chunk.onLoad();
|
||||||
|
|
||||||
|
EventDispatcher.call(new InstanceChunkLoadEvent(this, chunk));
|
||||||
|
final CompletableFuture<Chunk> future = this.loadingChunks.remove(index);
|
||||||
|
assert future == completableFuture : "Invalid future: " + future;
|
||||||
|
completableFuture.complete(chunk);
|
||||||
|
};
|
||||||
final Runnable retriever = () -> loader.loadChunk(this, chunkX, chunkZ)
|
final Runnable retriever = () -> loader.loadChunk(this, chunkX, chunkZ)
|
||||||
.thenCompose(chunk -> {
|
.thenCompose(chunk -> {
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
|
@ -303,14 +314,11 @@ public class InstanceContainer extends Instance {
|
||||||
})
|
})
|
||||||
// cache the retrieved chunk
|
// cache the retrieved chunk
|
||||||
.thenAccept(chunk -> {
|
.thenAccept(chunk -> {
|
||||||
// TODO run in the instance thread?
|
if (Thread.currentThread() instanceof TickSchedulerThread) {
|
||||||
cacheChunk(chunk);
|
postLoadCallback.accept(chunk); // Already running in instance tick thread
|
||||||
chunk.onLoad();
|
} else {
|
||||||
|
scheduleNextTick(ignored -> postLoadCallback.accept(chunk));
|
||||||
EventDispatcher.call(new InstanceChunkLoadEvent(this, chunk));
|
}
|
||||||
final CompletableFuture<Chunk> future = this.loadingChunks.remove(index);
|
|
||||||
assert future == completableFuture : "Invalid future: " + future;
|
|
||||||
completableFuture.complete(chunk);
|
|
||||||
})
|
})
|
||||||
.exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
MinecraftServer.getExceptionManager().handleException(throwable);
|
MinecraftServer.getExceptionManager().handleException(throwable);
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
|
|
||||||
|
import net.minestom.testing.Env;
|
||||||
|
import net.minestom.testing.EnvTest;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
|
||||||
|
@EnvTest
|
||||||
|
public class InstanceChunkLoadIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSyncChunkLoad(Env env) {
|
||||||
|
// Kind of a weird test because the test method is orchestrating ticks, meaning that the test thread is the tick thread.
|
||||||
|
|
||||||
|
var expectedThread = new AtomicReference<Thread>(null);
|
||||||
|
var instance = env.createFlatInstance(new NoopLoader(false));
|
||||||
|
instance.loadChunk(0, 0).thenRun(() -> {
|
||||||
|
// Will run in the thread which completed the future, which should always be the instance tick thread
|
||||||
|
expectedThread.set(Thread.currentThread());
|
||||||
|
});
|
||||||
|
env.tick(); // Tick once to run the scheduled task
|
||||||
|
|
||||||
|
assertSame(Thread.currentThread(), expectedThread.get(), "expected the callback to be executed in the same thread as the tick");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncChunkLoad(Env env) {
|
||||||
|
// Kind of a weird test because the test method is orchestrating ticks, meaning that the test thread is the tick thread.
|
||||||
|
|
||||||
|
var expectedThread = new AtomicReference<Thread>(null);
|
||||||
|
var instance = env.createFlatInstance(new NoopLoader(true));
|
||||||
|
instance.loadChunk(0, 0).thenRun(() -> {
|
||||||
|
// Will run in the thread which completed the future, which should always be the instance tick thread
|
||||||
|
expectedThread.set(Thread.currentThread());
|
||||||
|
});
|
||||||
|
env.tickWhile(() -> expectedThread.get() == null, Duration.ofSeconds(5)); // Tick once to run the scheduled task
|
||||||
|
|
||||||
|
assertSame(Thread.currentThread(), expectedThread.get(), "expected the callback to be executed in the same thread as the tick");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NoopLoader implements IChunkLoader {
|
||||||
|
private final boolean isParallel;
|
||||||
|
|
||||||
|
private NoopLoader(boolean isParallel) {
|
||||||
|
this.isParallel = isParallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsParallelLoading() {
|
||||||
|
return isParallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||||
|
return CompletableFuture.completedFuture(instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull CompletableFuture<Void> saveChunk(@NotNull Chunk chunk) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue