From b9679bc1ac2285184e735fa02edab206a507190f Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 11 Jul 2021 02:54:02 +0200 Subject: [PATCH] Initial future commit --- .../net/minestom/server/entity/Entity.java | 28 ++--- .../net/minestom/server/entity/Player.java | 6 +- .../minestom/server/instance/AnvilLoader.java | 32 +++--- .../server/instance/IChunkLoader.java | 36 +++---- .../minestom/server/instance/Instance.java | 98 ++++------------- .../server/instance/InstanceContainer.java | 101 +++++++++--------- .../server/instance/SharedInstance.java | 29 ++--- .../instance/batch/ChunkGenerationBatch.java | 8 +- .../server/utils/chunk/ChunkUtils.java | 21 ++-- 9 files changed, 138 insertions(+), 221 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index d9111a976..0b1166720 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -40,8 +40,6 @@ import net.minestom.server.potion.TimedPotion; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import net.minestom.server.thread.ThreadProvider; -import net.minestom.server.utils.callback.OptionalCallback; -import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.player.PlayerUtils; @@ -56,10 +54,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.UnaryOperator; @@ -257,30 +252,27 @@ public class Entity implements Viewable, Tickable, EventHandler, Da * @param chunks the chunk indexes to load before teleporting the entity, * indexes are from {@link ChunkUtils#getChunkIndex(int, int)}, * can be null or empty to only load the chunk at {@code position} - * @param callback the optional callback executed, even if auto chunk is not enabled * @throws IllegalStateException if you try to teleport an entity before settings its instance */ - public void teleport(@NotNull Pos position, @Nullable long[] chunks, @Nullable Runnable callback) { + public @NotNull CompletableFuture teleport(@NotNull Pos position, @Nullable long[] chunks) { Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!"); - final ChunkCallback endCallback = (chunk) -> { + CompletableFuture completableFuture = new CompletableFuture<>(); + final Runnable endCallback = () -> { refreshPosition(position); synchronizePosition(true); - OptionalCallback.execute(callback); + completableFuture.complete(null); }; if (chunks == null || chunks.length == 0) { - instance.loadOptionalChunk(position, endCallback); + instance.loadOptionalChunk(position).thenRun(endCallback); } else { - ChunkUtils.optionalLoadAll(instance, chunks, null, endCallback); + ChunkUtils.optionalLoadAll(instance, chunks, null).thenRun(endCallback); } + return completableFuture; } - public void teleport(@NotNull Pos position, @Nullable Runnable callback) { - teleport(position, null, callback); - } - - public void teleport(@NotNull Pos position) { - teleport(position, null); + public @NotNull CompletableFuture teleport(@NotNull Pos position) { + return teleport(position, null); } /** diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index f9b4e6aba..e76b13172 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -434,7 +434,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, refreshIsDead(false); // Runnable called when teleportation is successful (after loading and sending necessary chunk) - teleport(respawnEvent.getRespawnPosition(), this::refreshAfterTeleport); + teleport(respawnEvent.getRespawnPosition()).thenRun(this::refreshAfterTeleport); } @Override @@ -546,7 +546,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, final ChunkCallback endCallback = chunk -> spawnPlayer(instance, spawnPosition, firstSpawn, dimensionChange, true); - ChunkUtils.optionalLoadAll(instance, visibleChunks, null, endCallback); + ChunkUtils.optionalLoadAll(instance, visibleChunks, null).thenAccept(endCallback); } else { // The player already has the good version of all the chunks. // We just need to refresh his entity viewing list and add him to the instance @@ -1472,7 +1472,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); - this.instance.loadOptionalChunk(chunkX, chunkZ, chunk -> { + this.instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(chunk -> { if (chunk == null) { // Cannot load chunk (auto load is not enabled) return; diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java index 0d0122fe9..20384befe 100644 --- a/src/main/java/net/minestom/server/instance/AnvilLoader.java +++ b/src/main/java/net/minestom/server/instance/AnvilLoader.java @@ -5,8 +5,6 @@ import net.minestom.server.exception.ExceptionManager; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.instance.block.BlockManager; -import net.minestom.server.utils.callback.OptionalCallback; -import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.world.biomes.Biome; import net.minestom.server.world.biomes.BiomeManager; import org.jetbrains.annotations.NotNull; @@ -26,6 +24,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; public class AnvilLoader implements IChunkLoader { @@ -49,28 +48,27 @@ public class AnvilLoader implements IChunkLoader { } @Override - public boolean loadChunk(@NotNull Instance instance, int chunkX, int chunkZ, ChunkCallback callback) { + public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { LOGGER.debug("Attempt loading at {} {}", chunkX, chunkZ); if (!Files.exists(path)) { // No world folder - return false; + return CompletableFuture.completedFuture(null); } try { - final Chunk chunk = loadMCA(instance, chunkX, chunkZ, callback); - return chunk != null; + return loadMCA(instance, chunkX, chunkZ); } catch (IOException | AnvilException e) { EXCEPTION_MANAGER.handleException(e); } - return false; + return CompletableFuture.completedFuture(null); } - private @Nullable Chunk loadMCA(Instance instance, int chunkX, int chunkZ, ChunkCallback callback) throws IOException, AnvilException { + private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException, AnvilException { final RegionFile mcaFile = getMCAFile(chunkX, chunkZ); if (mcaFile == null) - return null; + return CompletableFuture.completedFuture(null); final ChunkColumn fileChunk = mcaFile.getChunk(chunkX, chunkZ); if (fileChunk == null) - return null; + return CompletableFuture.completedFuture(null); Biome[] biomes; if (fileChunk.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) { @@ -95,9 +93,8 @@ public class AnvilLoader implements IChunkLoader { section.setSkyLight(chunkSection.getSkyLights()); section.setBlockLight(chunkSection.getBlockLights()); } - OptionalCallback.execute(callback, chunk); mcaFile.forget(fileChunk); - return chunk; + return CompletableFuture.completedFuture(chunk); } private @Nullable RegionFile getMCAFile(int chunkX, int chunkZ) { @@ -174,8 +171,9 @@ public class AnvilLoader implements IChunkLoader { // TODO: find a way to unload MCAFiles when an entire region is unloaded + @Override - public void saveChunk(Chunk chunk, Runnable callback) { + public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { final int chunkX = chunk.getChunkX(); final int chunkZ = chunk.getChunkZ(); RegionFile mcaFile; @@ -198,7 +196,7 @@ public class AnvilLoader implements IChunkLoader { } catch (AnvilException | IOException e) { LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); EXCEPTION_MANAGER.handleException(e); - return; + return CompletableFuture.completedFuture(null); } } } @@ -208,7 +206,7 @@ public class AnvilLoader implements IChunkLoader { } catch (AnvilException | IOException e) { LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); EXCEPTION_MANAGER.handleException(e); - return; + return CompletableFuture.completedFuture(null); } save(chunk, column); try { @@ -217,9 +215,9 @@ public class AnvilLoader implements IChunkLoader { } catch (IOException e) { LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); EXCEPTION_MANAGER.handleException(e); - return; + return CompletableFuture.completedFuture(null); } - OptionalCallback.execute(callback); + return CompletableFuture.completedFuture(null); } private void save(Chunk chunk, ChunkColumn chunkColumn) { diff --git a/src/main/java/net/minestom/server/instance/IChunkLoader.java b/src/main/java/net/minestom/server/instance/IChunkLoader.java index 73223de1a..07423693d 100644 --- a/src/main/java/net/minestom/server/instance/IChunkLoader.java +++ b/src/main/java/net/minestom/server/instance/IChunkLoader.java @@ -2,12 +2,12 @@ package net.minestom.server.instance; import net.minestom.server.MinecraftServer; import net.minestom.server.utils.callback.OptionalCallback; -import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.thread.MinestomThread; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; @@ -24,53 +24,51 @@ public interface IChunkLoader { * @param instance the {@link Instance} where the {@link Chunk} belong * @param chunkX the chunk X * @param chunkZ the chunk Z - * @param callback the callback executed when the {@link Chunk} is done loading, - * never called if the method returns false. Can be null. - * @return true if the chunk loaded successfully, false otherwise + * @return a {@link CompletableFuture} containing the chunk, or null if not present */ - boolean loadChunk(@NotNull Instance instance, int chunkX, int chunkZ, @Nullable ChunkCallback callback); + @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ); /** * Saves a {@link Chunk} with an optional callback for when it is done. * - * @param chunk the {@link Chunk} to save - * @param callback the callback executed when the {@link Chunk} is done saving, - * should be called even if the saving failed (you can throw an exception). - * Can be null. + * @param chunk the {@link Chunk} to save + * @return a {@link CompletableFuture} executed when the {@link Chunk} is done saving, + * * should be called even if the saving failed (you can throw an exception). */ - void saveChunk(@NotNull Chunk chunk, @Nullable Runnable callback); + @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk); /** * Saves multiple chunks with an optional callback for when it is done. *

* Implementations need to check {@link #supportsParallelSaving()} to support the feature if possible. * - * @param chunks the chunks to save - * @param callback the callback executed when the {@link Chunk} is done saving, - * should be called even if the saving failed (you can throw an exception). - * Can be null. + * @param chunks the chunks to save + * @return a {@link CompletableFuture} executed when the {@link Chunk} is done saving, + * * should be called even if the saving failed (you can throw an exception). */ - default void saveChunks(@NotNull Collection chunks, @Nullable Runnable callback) { + default @NotNull CompletableFuture saveChunks(@NotNull Collection chunks) { if (supportsParallelSaving()) { ExecutorService parallelSavingThreadPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PARALLEL_CHUNK_SAVING, MinecraftServer.THREAD_NAME_PARALLEL_CHUNK_SAVING, true); - chunks.forEach(c -> parallelSavingThreadPool.execute(() -> saveChunk(c, null))); + chunks.forEach(c -> parallelSavingThreadPool.execute(() -> saveChunk(c))); try { parallelSavingThreadPool.shutdown(); parallelSavingThreadPool.awaitTermination(1L, java.util.concurrent.TimeUnit.DAYS); - OptionalCallback.execute(callback); } catch (InterruptedException e) { MinecraftServer.getExceptionManager().handleException(e); } + return CompletableFuture.completedFuture(null); } else { + CompletableFuture completableFuture = new CompletableFuture<>(); AtomicInteger counter = new AtomicInteger(); for (Chunk chunk : chunks) { - saveChunk(chunk, () -> { + saveChunk(chunk).whenComplete((unused, throwable) -> { final boolean isLast = counter.incrementAndGet() == chunks.size(); if (isLast) { - OptionalCallback.execute(callback); + completableFuture.complete(null); } }); } + return completableFuture; } } diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 192e68747..1e0b19014 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -32,7 +32,6 @@ import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import net.minestom.server.thread.ThreadProvider; import net.minestom.server.utils.PacketUtils; -import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.time.Cooldown; @@ -46,6 +45,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.time.Duration; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Consumer; @@ -165,23 +165,20 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta /** * Forces the generation of a {@link Chunk}, even if no file and {@link ChunkGenerator} are defined. * - * @param chunkX the chunk X - * @param chunkZ the chunk Z - * @param callback optional consumer called after the chunk has been generated, - * the returned chunk will never be null + * @param chunkX the chunk X + * @param chunkZ the chunk Z */ - public abstract void loadChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback); + public abstract CompletableFuture loadChunk(int chunkX, int chunkZ); /** * Loads the chunk if the chunk is already loaded or if * {@link #hasEnabledAutoChunkLoad()} returns true. * - * @param chunkX the chunk X - * @param chunkZ the chunk Z - * @param callback optional consumer called after the chunk has tried to be loaded, - * contains a chunk if it is successful, null otherwise + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return a {@link CompletableFuture} completed once the chunk has been processed */ - public abstract void loadOptionalChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback); + public abstract @NotNull CompletableFuture loadOptionalChunk(int chunkX, int chunkZ); /** * Schedules the removal of a {@link Chunk}, this method does not promise when it will be done. @@ -209,17 +206,16 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta /** * Saves a {@link Chunk} to permanent storage. * - * @param chunk the {@link Chunk} to save - * @param callback optional callback called when the {@link Chunk} is done saving + * @param chunk the {@link Chunk} to save */ - public abstract void saveChunkToStorage(@NotNull Chunk chunk, @Nullable Runnable callback); + public abstract CompletableFuture saveChunkToStorage(@NotNull Chunk chunk); /** * Saves multiple chunks to permanent storage. * * @param callback optional callback called when the chunks are done saving */ - public abstract void saveChunksToStorage(@Nullable Runnable callback); + public abstract CompletableFuture saveChunksToStorage(); /** * Gets the instance {@link ChunkGenerator}. @@ -259,35 +255,6 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta */ public abstract void setStorageLocation(@Nullable StorageLocation storageLocation); - /** - * Used when a {@link Chunk} is not currently loaded in memory and need to be retrieved from somewhere else. - * Could be read from disk, or generated from scratch. - *

- * Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Chunk)} and to cache - * that this chunk has been loaded. - *

- * WARNING: it has to retrieve a chunk, this is not optional and should execute the callback in all case. - * - * @param chunkX the chunk X - * @param chunkZ the chunk X - * @param callback the optional callback executed once the {@link Chunk} has been retrieved - */ - protected abstract void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback); - - /** - * Called to generated a new {@link Chunk} from scratch. - *

- * Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Chunk)} and to cache - * that this chunk has been loaded. - *

- * This is where you can put your chunk generation code. - * - * @param chunkX the chunk X - * @param chunkZ the chunk Z - * @param callback the optional callback executed with the newly created {@link Chunk} - */ - protected abstract void createChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback); - /** * When set to true, chunks will load automatically when requested. * Otherwise using {@link #loadChunk(int, int)} will be required to even spawn a player @@ -519,41 +486,28 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta return Collections.unmodifiableSet(entities); } - /** - * Loads the {@link Chunk} at the given position without any callback. - *

- * WARNING: this is a non-blocking task. - * - * @param chunkX the chunk X - * @param chunkZ the chunk Z - */ - public void loadChunk(int chunkX, int chunkZ) { - loadChunk(chunkX, chunkZ, null); - } - /** * Loads the chunk at the given {@link Point} with a callback. * * @param point the chunk position - * @param callback the optional callback to run when the chunk is loaded */ - public void loadChunk(@NotNull Point point, @Nullable ChunkCallback callback) { + public CompletableFuture loadChunk(@NotNull Point point) { final int chunkX = ChunkUtils.getChunkCoordinate(point.x()); final int chunkZ = ChunkUtils.getChunkCoordinate(point.z()); - loadChunk(chunkX, chunkZ, callback); + return loadChunk(chunkX, chunkZ); } /** * Loads a {@link Chunk} (if {@link #hasEnabledAutoChunkLoad()} returns true) * at the given {@link Point} with a callback. * - * @param point the chunk position - * @param callback the optional callback executed when the chunk is loaded (or with a null chunk if not) + * @param point the chunk position + * @return a {@link CompletableFuture} completed once the chunk has been processed */ - public void loadOptionalChunk(@NotNull Point point, @Nullable ChunkCallback callback) { + public @NotNull CompletableFuture loadOptionalChunk(@NotNull Point point) { final int chunkX = ChunkUtils.getChunkCoordinate(point.x()); final int chunkZ = ChunkUtils.getChunkCoordinate(point.z()); - loadOptionalChunk(chunkX, chunkZ, callback); + return loadOptionalChunk(chunkX, chunkZ); } /** @@ -633,22 +587,6 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta return getChunk(chunkX, chunkZ) != null; } - /** - * Saves a {@link Chunk} without any callback. - * - * @param chunk the chunk to save - */ - public void saveChunkToStorage(@NotNull Chunk chunk) { - saveChunkToStorage(chunk, null); - } - - /** - * Saves all {@link Chunk} without any callback. - */ - public void saveChunksToStorage() { - saveChunksToStorage(null); - } - /** * Gets the instance unique id. * @@ -723,7 +661,7 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta }); // Load the chunk if not already (or throw an error if auto chunk load is disabled) - loadOptionalChunk(entityPosition, chunk -> { + loadOptionalChunk(entityPosition).thenAccept(chunk -> { Check.notNull(chunk, "You tried to spawn an entity in an unloaded chunk, " + entityPosition); UNSAFE_addEntityToChunk(entity, chunk); }); diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index ce0928dd7..defc672f2 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -19,10 +19,7 @@ import net.minestom.server.network.packet.server.play.BlockChangePacket; import net.minestom.server.network.packet.server.play.EffectPacket; import net.minestom.server.network.packet.server.play.UnloadChunkPacket; import net.minestom.server.storage.StorageLocation; -import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.PacketUtils; -import net.minestom.server.utils.callback.OptionalCallback; -import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkSupplier; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.validate.Check; @@ -32,11 +29,13 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; /** * InstanceContainer is an instance that contains chunks in contrary to SharedInstance. @@ -114,7 +113,7 @@ public class InstanceContainer extends Instance { "Tried to set a block to an unloaded chunk with auto chunk load disabled"); final int chunkX = ChunkUtils.getChunkCoordinate(x); final int chunkZ = ChunkUtils.getChunkCoordinate(z); - loadChunk(chunkX, chunkZ, c -> UNSAFE_setBlock(c, x, y, z, block, null, null)); + loadChunk(chunkX, chunkZ).thenAccept(c -> UNSAFE_setBlock(c, x, y, z, block, null, null)); } } @@ -239,30 +238,30 @@ public class InstanceContainer extends Instance { } @Override - public void loadChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { + public CompletableFuture loadChunk(int chunkX, int chunkZ) { final Chunk chunk = getChunk(chunkX, chunkZ); if (chunk != null) { // Chunk already loaded - OptionalCallback.execute(callback, chunk); + return CompletableFuture.completedFuture(chunk); } else { // Retrieve chunk from somewhere else (file or create a new one using the ChunkGenerator) - retrieveChunk(chunkX, chunkZ, callback); + return retrieveChunk(chunkX, chunkZ); } } @Override - public void loadOptionalChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { + public @NotNull CompletableFuture loadOptionalChunk(int chunkX, int chunkZ) { final Chunk chunk = getChunk(chunkX, chunkZ); if (chunk != null) { // Chunk already loaded - OptionalCallback.execute(callback, chunk); + return CompletableFuture.completedFuture(chunk); } else { if (hasEnabledAutoChunkLoad()) { // Use `IChunkLoader` or `ChunkGenerator` - retrieveChunk(chunkX, chunkZ, callback); + return retrieveChunk(chunkX, chunkZ); } else { // Chunk not loaded, return null - OptionalCallback.execute(callback, null); + return CompletableFuture.completedFuture(null); } } } @@ -287,13 +286,11 @@ public class InstanceContainer extends Instance { } /** - * Saves the instance ({@link #getUniqueId()} {@link #getData()}) and call {@link #saveChunksToStorage(Runnable)}. + * Saves the instance ({@link #getUniqueId()} {@link #getData()}) and call {@link #saveChunksToStorage()}. *

* WARNING: {@link #getData()} needs to be a {@link SerializableData} in order to be saved. - * - * @param callback the optional callback once the saving is done */ - public void saveInstance(@Nullable Runnable callback) { + public CompletableFuture saveInstance() { Check.notNull(getStorageLocation(), "You cannot save the instance if no StorageLocation has been defined"); this.storageLocation.set(UUID_KEY, getUniqueId(), UUID.class); @@ -305,49 +302,45 @@ public class InstanceContainer extends Instance { this.storageLocation.set(DATA_KEY, (SerializableData) getData(), SerializableData.class); } - saveChunksToStorage(callback); - } - - /** - * Saves the instance without callback. - * - * @see #saveInstance(Runnable) - */ - public void saveInstance() { - saveInstance(null); + return saveChunksToStorage(); } @Override - public void saveChunkToStorage(@NotNull Chunk chunk, Runnable callback) { - this.chunkLoader.saveChunk(chunk, callback); + public CompletableFuture saveChunkToStorage(@NotNull Chunk chunk) { + return chunkLoader.saveChunk(chunk); } @Override - public void saveChunksToStorage(@Nullable Runnable callback) { + public CompletableFuture saveChunksToStorage() { Collection chunksCollection = chunks.values(); - this.chunkLoader.saveChunks(chunksCollection, callback); + return chunkLoader.saveChunks(chunksCollection); } - @Override - protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { - final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> { - cacheChunk(chunk); - UPDATE_MANAGER.signalChunkLoad(chunk); - // Execute callback and event in the instance thread - scheduleNextTick(instance -> { - callChunkLoadEvent(chunkX, chunkZ); - OptionalCallback.execute(callback, chunk); - }); - }); + protected CompletableFuture retrieveChunk(int chunkX, int chunkZ) { + CompletableFuture completableFuture = new CompletableFuture<>(); + chunkLoader.loadChunk(this, chunkX, chunkZ) + .whenComplete((chunk, throwable) -> { + if (chunk != null) { + // Successfully loaded + cacheChunk(chunk); + UPDATE_MANAGER.signalChunkLoad(chunk); + // Execute callback and event in the instance thread + scheduleNextTick(instance -> { + callChunkLoadEvent(chunkX, chunkZ); + completableFuture.complete(chunk); + }); + } else { + // Not present + createChunk(chunkX, chunkZ).whenComplete((c, t) -> + completableFuture.complete(c)); + } + }); - if (!loaded) { - // Not found, create a new chunk - createChunk(chunkX, chunkZ, callback); - } + // Chunk is being loaded + return completableFuture; } - @Override - protected void createChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { + protected CompletableFuture createChunk(int chunkX, int chunkZ) { Biome[] biomes = new Biome[Biome.getBiomeCount(getDimensionType())]; if (chunkGenerator == null) { Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0)); @@ -360,18 +353,22 @@ public class InstanceContainer extends Instance { cacheChunk(chunk); + final Consumer chunkRegisterCallback = (c) -> { + UPDATE_MANAGER.signalChunkLoad(c); + callChunkLoadEvent(chunkX, chunkZ); + }; + if (chunkGenerator != null && chunk.shouldGenerate()) { // Execute the chunk generator to populate the chunk final ChunkGenerationBatch chunkBatch = new ChunkGenerationBatch(this, chunk); - chunkBatch.generate(chunkGenerator, callback); + return chunkBatch.generate(chunkGenerator) + .whenComplete((c, t) -> chunkRegisterCallback.accept(c)); } else { // No chunk generator, execute the callback with the empty chunk - OptionalCallback.execute(callback, chunk); + return CompletableFuture.completedFuture(chunk) + .whenComplete((c, t) -> chunkRegisterCallback.accept(c)); } - - UPDATE_MANAGER.signalChunkLoad(chunk); - callChunkLoadEvent(chunkX, chunkZ); } @Override @@ -396,7 +393,7 @@ public class InstanceContainer extends Instance { * Uses {@link DynamicChunk} by default. *

* WARNING: if you need to save this instance's chunks later, - * the code needs to be predictable for {@link IChunkLoader#loadChunk(Instance, int, int, ChunkCallback)} + * the code needs to be predictable for {@link IChunkLoader#loadChunk(Instance, int, int)} * to create the correct type of {@link Chunk}. tl;dr: Need chunk save = no random type. * * @param chunkSupplier the new {@link ChunkSupplier} of this instance, chunks need to be non-null diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java index 0dab7b474..fbf676aa7 100644 --- a/src/main/java/net/minestom/server/instance/SharedInstance.java +++ b/src/main/java/net/minestom/server/instance/SharedInstance.java @@ -1,16 +1,17 @@ package net.minestom.server.instance; +import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Player; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.storage.StorageLocation; import net.minestom.server.utils.chunk.ChunkCallback; -import net.minestom.server.coordinate.Point; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.UUID; +import java.util.concurrent.CompletableFuture; /** * The {@link SharedInstance} is an instance that shares the same chunks as its linked {@link InstanceContainer}, @@ -42,13 +43,13 @@ public class SharedInstance extends Instance { } @Override - public void loadChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { - this.instanceContainer.loadChunk(chunkX, chunkZ, callback); + public CompletableFuture loadChunk(int chunkX, int chunkZ) { + return instanceContainer.loadChunk(chunkX, chunkZ); } @Override - public void loadOptionalChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { - this.instanceContainer.loadOptionalChunk(chunkX, chunkZ, callback); + public @NotNull CompletableFuture loadOptionalChunk(int chunkX, int chunkZ) { + return instanceContainer.loadOptionalChunk(chunkX, chunkZ); } @Override @@ -62,13 +63,13 @@ public class SharedInstance extends Instance { } @Override - public void saveChunkToStorage(@NotNull Chunk chunk, @Nullable Runnable callback) { - this.instanceContainer.saveChunkToStorage(chunk, callback); + public CompletableFuture saveChunkToStorage(@NotNull Chunk chunk) { + return instanceContainer.saveChunkToStorage(chunk); } @Override - public void saveChunksToStorage(@Nullable Runnable callback) { - instanceContainer.saveChunksToStorage(callback); + public CompletableFuture saveChunksToStorage() { + return instanceContainer.saveChunksToStorage(); } @Override @@ -97,16 +98,6 @@ public class SharedInstance extends Instance { this.instanceContainer.setStorageLocation(storageLocation); } - @Override - public void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { - this.instanceContainer.retrieveChunk(chunkX, chunkZ, callback); - } - - @Override - protected void createChunk(int chunkX, int chunkZ, ChunkCallback callback) { - this.instanceContainer.createChunk(chunkX, chunkZ, callback); - } - @Override public void enableAutoChunkLoad(boolean enable) { instanceContainer.enableAutoChunkLoad(enable); diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java index 2add11811..ba04d7a72 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.concurrent.CompletableFuture; public class ChunkGenerationBatch extends ChunkBatch { private final InstanceContainer instance; @@ -23,7 +24,8 @@ public class ChunkGenerationBatch extends ChunkBatch { chunk.setBlock(x, y, z, block); } - public void generate(@NotNull ChunkGenerator chunkGenerator, @Nullable ChunkCallback callback) { + public CompletableFuture generate(@NotNull ChunkGenerator chunkGenerator) { + final CompletableFuture completableFuture = new CompletableFuture<>(); BLOCK_BATCH_POOL.execute(() -> { synchronized (chunk) { final List populators = chunkGenerator.getPopulators(); @@ -40,10 +42,10 @@ public class ChunkGenerationBatch extends ChunkBatch { // Update the chunk. this.chunk.sendChunk(); this.instance.refreshLastBlockChangeTime(); - if (callback != null) - this.instance.scheduleNextTick(inst -> callback.accept(this.chunk)); + completableFuture.complete(chunk); } }); + return completableFuture; } @Override diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index a92028dda..0ad525306 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -4,12 +4,12 @@ import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; -import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.callback.OptionalCallback; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; public final class ChunkUtils { @@ -19,23 +19,23 @@ public final class ChunkUtils { } /** - * Executes {@link Instance#loadOptionalChunk(int, int, ChunkCallback)} for the array of chunks {@code chunks} + * Executes {@link Instance#loadOptionalChunk(int, int)} for the array of chunks {@code chunks} * with multiple callbacks, {@code eachCallback} which is executed each time a new chunk is loaded and * {@code endCallback} when all the chunks in the array have been loaded. *

- * Be aware that {@link Instance#loadOptionalChunk(int, int, ChunkCallback)} can give a null chunk in the callback + * Be aware that {@link Instance#loadOptionalChunk(int, int)} can give a null chunk in the callback * if {@link Instance#hasEnabledAutoChunkLoad()} returns false and the chunk is not already loaded. * * @param instance the instance to load the chunks from * @param chunks the chunks to loaded, long value from {@link #getChunkIndex(int, int)} * @param eachCallback the optional callback when a chunk get loaded - * @param endCallback the optional callback when all the chunks have been loaded + * @return a {@link CompletableFuture} completed once all chunks have been processed */ - public static void optionalLoadAll(@NotNull Instance instance, long @NotNull [] chunks, - @Nullable ChunkCallback eachCallback, @Nullable ChunkCallback endCallback) { + public static CompletableFuture optionalLoadAll(@NotNull Instance instance, long @NotNull [] chunks, + @Nullable ChunkCallback eachCallback) { + CompletableFuture completableFuture = new CompletableFuture<>(); final int length = chunks.length; AtomicInteger counter = new AtomicInteger(0); - for (long visibleChunk : chunks) { final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk); final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk); @@ -45,16 +45,17 @@ public final class ChunkUtils { final boolean isLast = counter.get() == length - 1; if (isLast) { // This is the last chunk to be loaded , spawn player - OptionalCallback.execute(endCallback, chunk); + completableFuture.complete(chunk); } else { // Increment the counter of current loaded chunks counter.incrementAndGet(); } }; - // WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck. - instance.loadOptionalChunk(chunkX, chunkZ, callback); + // WARNING: if auto-load is disabled and no chunks are loaded beforehand, player will be stuck. + instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(callback); } + return completableFuture; } /**