Merge pull request #366 from Minestom/new-block-future

Initial future commit
This commit is contained in:
TheMode 2021-07-11 14:07:34 +02:00 committed by GitHub
commit 44edeb585d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 216 additions and 288 deletions

View File

@ -40,8 +40,7 @@ import net.minestom.server.potion.TimedPotion;
import net.minestom.server.tag.Tag; import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagHandler;
import net.minestom.server.thread.ThreadProvider; import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.chunk.ChunkCallback;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.player.PlayerUtils;
@ -56,10 +55,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.time.Duration; import java.time.Duration;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
@ -257,30 +253,27 @@ public class Entity implements Viewable, Tickable, EventHandler<EntityEvent>, Da
* @param chunks the chunk indexes to load before teleporting the entity, * @param chunks the chunk indexes to load before teleporting the entity,
* indexes are from {@link ChunkUtils#getChunkIndex(int, int)}, * indexes are from {@link ChunkUtils#getChunkIndex(int, int)},
* can be null or empty to only load the chunk at {@code position} * 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 * @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<Void> teleport(@NotNull Pos position, @Nullable long[] chunks) {
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!"); Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
final ChunkCallback endCallback = (chunk) -> { CompletableFuture<Void> completableFuture = new CompletableFuture<>();
final Runnable endCallback = () -> {
refreshPosition(position); refreshPosition(position);
synchronizePosition(true); synchronizePosition(true);
OptionalCallback.execute(callback); completableFuture.complete(null);
}; };
if (chunks == null || chunks.length == 0) { if (chunks == null || chunks.length == 0) {
instance.loadOptionalChunk(position, endCallback); instance.loadOptionalChunk(position).thenRun(endCallback);
} else { } 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) { public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position) {
teleport(position, null, callback); return teleport(position, null);
}
public void teleport(@NotNull Pos position) {
teleport(position, null);
} }
/** /**
@ -852,9 +845,11 @@ public class Entity implements Viewable, Tickable, EventHandler<EntityEvent>, Da
* *
* @param instance the new instance of the entity * @param instance the new instance of the entity
* @param spawnPosition the spawn position for the entity. * @param spawnPosition the spawn position for the entity.
* @return a {@link CompletableFuture} called once the entity's instance has been set,
* this is due to chunks needing to load for players
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager} * @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
*/ */
public void setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) { public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
Check.stateCondition(!instance.isRegistered(), Check.stateCondition(!instance.isRegistered(),
"Instances need to be registered, please use InstanceManager#registerInstance or InstanceManager#registerSharedInstance"); "Instances need to be registered, please use InstanceManager#registerInstance or InstanceManager#registerSharedInstance");
if (this.instance != null) { if (this.instance != null) {
@ -867,21 +862,24 @@ public class Entity implements Viewable, Tickable, EventHandler<EntityEvent>, Da
instance.UNSAFE_addEntity(this); instance.UNSAFE_addEntity(this);
spawn(); spawn();
EventDispatcher.call(new EntitySpawnEvent(this, instance)); EventDispatcher.call(new EntitySpawnEvent(this, instance));
return AsyncUtils.NULL_FUTURE;
} }
public void setInstance(@NotNull Instance instance, @NotNull Point spawnPosition) { public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Point spawnPosition) {
setInstance(instance, Pos.fromPoint(spawnPosition)); return setInstance(instance, Pos.fromPoint(spawnPosition));
} }
/** /**
* Changes the entity instance. * Changes the entity instance.
* *
* @param instance the new instance of the entity * @param instance the new instance of the entity
* @return a {@link CompletableFuture} called once the entity's instance has been set,
* this is due to chunks needing to load for players
* @throws NullPointerException if {@code instance} is null * @throws NullPointerException if {@code instance} is null
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager} * @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
*/ */
public void setInstance(@NotNull Instance instance) { public CompletableFuture<Void> setInstance(@NotNull Instance instance) {
setInstance(instance, this.position); return setInstance(instance, this.position);
} }
/** /**

View File

@ -18,6 +18,7 @@ import java.time.Duration;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
public class EntityCreature extends LivingEntity implements NavigableEntity, EntityAI { public class EntityCreature extends LivingEntity implements NavigableEntity, EntityAI {
@ -55,10 +56,10 @@ public class EntityCreature extends LivingEntity implements NavigableEntity, Ent
} }
@Override @Override
public void setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) { public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace())); this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace()));
super.setInstance(instance, spawnPosition); return super.setInstance(instance, spawnPosition);
} }
@Override @Override

View File

@ -69,7 +69,7 @@ import net.minestom.server.sound.SoundCategory;
import net.minestom.server.sound.SoundEvent; import net.minestom.server.sound.SoundEvent;
import net.minestom.server.stat.PlayerStatistic; import net.minestom.server.stat.PlayerStatistic;
import net.minestom.server.utils.*; import net.minestom.server.utils.*;
import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.identity.NamedAndIdentified;
@ -86,6 +86,7 @@ import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -434,7 +435,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
refreshIsDead(false); refreshIsDead(false);
// Runnable called when teleportation is successful (after loading and sending necessary chunk) // Runnable called when teleportation is successful (after loading and sending necessary chunk)
teleport(respawnEvent.getRespawnPosition(), this::refreshAfterTeleport); teleport(respawnEvent.getRespawnPosition()).thenRun(this::refreshAfterTeleport);
} }
@Override @Override
@ -521,9 +522,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* *
* @param instance the new player instance * @param instance the new player instance
* @param spawnPosition the new position of the player * @param spawnPosition the new position of the player
* @return
*/ */
@Override @Override
public void setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) { public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
Check.argCondition(this.instance == instance, "Instance should be different than the current one"); Check.argCondition(this.instance == instance, "Instance should be different than the current one");
// true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance) // true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance)
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance) || final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance) ||
@ -543,14 +545,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Only load the spawning chunk to speed up login, remaining chunks are loaded in #spawnPlayer // Only load the spawning chunk to speed up login, remaining chunks are loaded in #spawnPlayer
final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, 0); final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, 0);
final ChunkCallback endCallback = return ChunkUtils.optionalLoadAll(instance, visibleChunks, null)
chunk -> spawnPlayer(instance, spawnPosition, firstSpawn, dimensionChange, true); .thenAccept(chunk -> spawnPlayer(instance, spawnPosition, firstSpawn, dimensionChange, true));
ChunkUtils.optionalLoadAll(instance, visibleChunks, null, endCallback);
} else { } else {
// The player already has the good version of all the chunks. // 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 // We just need to refresh his entity viewing list and add him to the instance
spawnPlayer(instance, spawnPosition, false, false, false); spawnPlayer(instance, spawnPosition, false, false, false);
return AsyncUtils.NULL_FUTURE;
} }
} }
@ -559,11 +560,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* if the player is not in any instance). * if the player is not in any instance).
* *
* @param instance the new player instance * @param instance the new player instance
* @return a {@link CompletableFuture} called once the entity's instance has been set,
* this is due to chunks needing to load for players
* @see #setInstance(Instance, Pos) * @see #setInstance(Instance, Pos)
*/ */
@Override @Override
public void setInstance(@NotNull Instance instance) { public CompletableFuture<Void> setInstance(@NotNull Instance instance) {
setInstance(instance, this.instance != null ? getPosition() : getRespawnPoint()); return setInstance(instance, this.instance != null ? getPosition() : getRespawnPoint());
} }
/** /**
@ -1472,7 +1475,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
this.instance.loadOptionalChunk(chunkX, chunkZ, chunk -> { this.instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(chunk -> {
if (chunk == null) { if (chunk == null) {
// Cannot load chunk (auto load is not enabled) // Cannot load chunk (auto load is not enabled)
return; return;

View File

@ -3,6 +3,7 @@ package net.minestom.server.entity.fakeplayer;
import com.extollit.gaming.ai.path.HydrazinePathFinder; import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.attribute.Attribute; import net.minestom.server.attribute.Attribute;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.NavigableEntity; import net.minestom.server.entity.pathfinding.NavigableEntity;
import net.minestom.server.entity.pathfinding.Navigator; import net.minestom.server.entity.pathfinding.Navigator;
@ -16,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@ -118,10 +120,10 @@ public class FakePlayer extends Player implements NavigableEntity {
} }
@Override @Override
public void setInstance(@NotNull Instance instance) { public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace())); this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace()));
super.setInstance(instance); return super.setInstance(instance, spawnPosition);
} }
@Override @Override

View File

@ -5,8 +5,7 @@ import net.minestom.server.exception.ExceptionManager;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.chunk.ChunkCallback;
import net.minestom.server.world.biomes.Biome; import net.minestom.server.world.biomes.Biome;
import net.minestom.server.world.biomes.BiomeManager; import net.minestom.server.world.biomes.BiomeManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -26,6 +25,7 @@ import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class AnvilLoader implements IChunkLoader { public class AnvilLoader implements IChunkLoader {
@ -49,28 +49,27 @@ public class AnvilLoader implements IChunkLoader {
} }
@Override @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); LOGGER.debug("Attempt loading at {} {}", chunkX, chunkZ);
if (!Files.exists(path)) { if (!Files.exists(path)) {
// No world folder // No world folder
return false; return CompletableFuture.completedFuture(null);
} }
try { try {
final Chunk chunk = loadMCA(instance, chunkX, chunkZ, callback); return loadMCA(instance, chunkX, chunkZ);
return chunk != null;
} catch (IOException | AnvilException e) { } catch (IOException | AnvilException e) {
EXCEPTION_MANAGER.handleException(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); final RegionFile mcaFile = getMCAFile(chunkX, chunkZ);
if (mcaFile == null) if (mcaFile == null)
return null; return CompletableFuture.completedFuture(null);
final ChunkColumn fileChunk = mcaFile.getChunk(chunkX, chunkZ); final ChunkColumn fileChunk = mcaFile.getChunk(chunkX, chunkZ);
if (fileChunk == null) if (fileChunk == null)
return null; return CompletableFuture.completedFuture(null);
Biome[] biomes; Biome[] biomes;
if (fileChunk.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) { if (fileChunk.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) {
@ -95,9 +94,8 @@ public class AnvilLoader implements IChunkLoader {
section.setSkyLight(chunkSection.getSkyLights()); section.setSkyLight(chunkSection.getSkyLights());
section.setBlockLight(chunkSection.getBlockLights()); section.setBlockLight(chunkSection.getBlockLights());
} }
OptionalCallback.execute(callback, chunk);
mcaFile.forget(fileChunk); mcaFile.forget(fileChunk);
return chunk; return CompletableFuture.completedFuture(chunk);
} }
private @Nullable RegionFile getMCAFile(int chunkX, int chunkZ) { private @Nullable RegionFile getMCAFile(int chunkX, int chunkZ) {
@ -174,8 +172,9 @@ public class AnvilLoader implements IChunkLoader {
// TODO: find a way to unload MCAFiles when an entire region is unloaded // TODO: find a way to unload MCAFiles when an entire region is unloaded
@Override @Override
public void saveChunk(Chunk chunk, Runnable callback) { public @NotNull CompletableFuture<Void> saveChunk(@NotNull Chunk chunk) {
final int chunkX = chunk.getChunkX(); final int chunkX = chunk.getChunkX();
final int chunkZ = chunk.getChunkZ(); final int chunkZ = chunk.getChunkZ();
RegionFile mcaFile; RegionFile mcaFile;
@ -198,7 +197,7 @@ public class AnvilLoader implements IChunkLoader {
} catch (AnvilException | IOException e) { } catch (AnvilException | IOException e) {
LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e);
EXCEPTION_MANAGER.handleException(e); EXCEPTION_MANAGER.handleException(e);
return; return AsyncUtils.NULL_FUTURE;
} }
} }
} }
@ -208,7 +207,7 @@ public class AnvilLoader implements IChunkLoader {
} catch (AnvilException | IOException e) { } catch (AnvilException | IOException e) {
LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e);
EXCEPTION_MANAGER.handleException(e); EXCEPTION_MANAGER.handleException(e);
return; return AsyncUtils.NULL_FUTURE;
} }
save(chunk, column); save(chunk, column);
try { try {
@ -217,9 +216,9 @@ public class AnvilLoader implements IChunkLoader {
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e);
EXCEPTION_MANAGER.handleException(e); EXCEPTION_MANAGER.handleException(e);
return; return AsyncUtils.NULL_FUTURE;
} }
OptionalCallback.execute(callback); return AsyncUtils.NULL_FUTURE;
} }
private void save(Chunk chunk, ChunkColumn chunkColumn) { private void save(Chunk chunk, ChunkColumn chunkColumn) {
@ -256,4 +255,14 @@ public class AnvilLoader implements IChunkLoader {
} }
chunkColumn.setTileEntities(tileEntities); chunkColumn.setTileEntities(tileEntities);
} }
@Override
public boolean supportsParallelLoading() {
return true;
}
@Override
public boolean supportsParallelSaving() {
return true;
}
} }

View File

@ -1,13 +1,13 @@
package net.minestom.server.instance; package net.minestom.server.instance;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.chunk.ChunkCallback;
import net.minestom.server.utils.thread.MinestomThread; import net.minestom.server.utils.thread.MinestomThread;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -24,53 +24,51 @@ public interface IChunkLoader {
* @param instance the {@link Instance} where the {@link Chunk} belong * @param instance the {@link Instance} where the {@link Chunk} belong
* @param chunkX the chunk X * @param chunkX the chunk X
* @param chunkZ the chunk Z * @param chunkZ the chunk Z
* @param callback the callback executed when the {@link Chunk} is done loading, * @return a {@link CompletableFuture} containing the chunk, or null if not present
* never called if the method returns false. Can be null.
* @return true if the chunk loaded successfully, false otherwise
*/ */
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. * Saves a {@link Chunk} with an optional callback for when it is done.
* *
* @param chunk the {@link Chunk} to save * @param chunk the {@link Chunk} to save
* @param callback the callback executed when the {@link Chunk} is done saving, * @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). * should be called even if the saving failed (you can throw an exception).
* Can be null.
*/ */
void saveChunk(@NotNull Chunk chunk, @Nullable Runnable callback); @NotNull CompletableFuture<Void> saveChunk(@NotNull Chunk chunk);
/** /**
* Saves multiple chunks with an optional callback for when it is done. * Saves multiple chunks with an optional callback for when it is done.
* <p> * <p>
* Implementations need to check {@link #supportsParallelSaving()} to support the feature if possible. * Implementations need to check {@link #supportsParallelSaving()} to support the feature if possible.
* *
* @param chunks the chunks to save * @param chunks the chunks to save
* @param callback the callback executed when the {@link Chunk} is done saving, * @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). * should be called even if the saving failed (you can throw an exception).
* Can be null.
*/ */
default void saveChunks(@NotNull Collection<Chunk> chunks, @Nullable Runnable callback) { default @NotNull CompletableFuture<Void> saveChunks(@NotNull Collection<Chunk> chunks) {
if (supportsParallelSaving()) { if (supportsParallelSaving()) {
ExecutorService parallelSavingThreadPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PARALLEL_CHUNK_SAVING, MinecraftServer.THREAD_NAME_PARALLEL_CHUNK_SAVING, true); 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 { try {
parallelSavingThreadPool.shutdown(); parallelSavingThreadPool.shutdown();
parallelSavingThreadPool.awaitTermination(1L, java.util.concurrent.TimeUnit.DAYS); parallelSavingThreadPool.awaitTermination(1L, java.util.concurrent.TimeUnit.DAYS);
OptionalCallback.execute(callback);
} catch (InterruptedException e) { } catch (InterruptedException e) {
MinecraftServer.getExceptionManager().handleException(e); MinecraftServer.getExceptionManager().handleException(e);
} }
return AsyncUtils.NULL_FUTURE;
} else { } else {
CompletableFuture<Void> completableFuture = new CompletableFuture<>();
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
for (Chunk chunk : chunks) { for (Chunk chunk : chunks) {
saveChunk(chunk, () -> { saveChunk(chunk).whenComplete((unused, throwable) -> {
final boolean isLast = counter.incrementAndGet() == chunks.size(); final boolean isLast = counter.incrementAndGet() == chunks.size();
if (isLast) { if (isLast) {
OptionalCallback.execute(callback); completableFuture.complete(null);
} }
}); });
} }
return completableFuture;
} }
} }

View File

@ -32,7 +32,6 @@ import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagHandler;
import net.minestom.server.thread.ThreadProvider; import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.PacketUtils; 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.chunk.ChunkUtils;
import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.time.Cooldown; import net.minestom.server.utils.time.Cooldown;
@ -46,6 +45,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.time.Duration; import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -165,23 +165,43 @@ 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. * Forces the generation of a {@link Chunk}, even if no file and {@link ChunkGenerator} are defined.
* *
* @param chunkX the chunk X * @param chunkX the chunk X
* @param chunkZ the chunk Z * @param chunkZ the chunk Z
* @param callback optional consumer called after the chunk has been generated, * @return a {@link CompletableFuture} completed once the chunk has been loaded
* the returned chunk will never be null
*/ */
public abstract void loadChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback); public abstract @NotNull CompletableFuture<@NotNull Chunk> loadChunk(int chunkX, int chunkZ);
/**
* Loads the chunk at the given {@link Point} with a callback.
*
* @param point the chunk position
*/
public @NotNull CompletableFuture<@NotNull Chunk> loadChunk(@NotNull Point point) {
return loadChunk(ChunkUtils.getChunkCoordinate(point.x()),
ChunkUtils.getChunkCoordinate(point.z()));
}
/** /**
* Loads the chunk if the chunk is already loaded or if * Loads the chunk if the chunk is already loaded or if
* {@link #hasEnabledAutoChunkLoad()} returns true. * {@link #hasEnabledAutoChunkLoad()} returns true.
* *
* @param chunkX the chunk X * @param chunkX the chunk X
* @param chunkZ the chunk Z * @param chunkZ the chunk Z
* @param callback optional consumer called after the chunk has tried to be loaded, * @return a {@link CompletableFuture} completed once the chunk has been processed, can be null if not loaded
* contains a chunk if it is successful, null otherwise
*/ */
public abstract void loadOptionalChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback); public abstract @NotNull CompletableFuture<@Nullable Chunk> loadOptionalChunk(int chunkX, int chunkZ);
/**
* Loads a {@link Chunk} (if {@link #hasEnabledAutoChunkLoad()} returns true)
* at the given {@link Point} with a callback.
*
* @param point the chunk position
* @return a {@link CompletableFuture} completed once the chunk has been processed, null if not loaded
*/
public @NotNull CompletableFuture<@Nullable Chunk> loadOptionalChunk(@NotNull Point point) {
return loadOptionalChunk(ChunkUtils.getChunkCoordinate(point.x()),
ChunkUtils.getChunkCoordinate(point.z()));
}
/** /**
* Schedules the removal of a {@link Chunk}, this method does not promise when it will be done. * Schedules the removal of a {@link Chunk}, this method does not promise when it will be done.
@ -194,6 +214,18 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
*/ */
public abstract void unloadChunk(@NotNull Chunk chunk); public abstract void unloadChunk(@NotNull Chunk chunk);
/**
* Unloads the chunk at the given position.
*
* @param chunkX the chunk X
* @param chunkZ the chunk Z
*/
public void unloadChunk(int chunkX, int chunkZ) {
final Chunk chunk = getChunk(chunkX, chunkZ);
Check.notNull(chunk, "The chunk at {0}:{1} is already unloaded", chunkX, chunkZ);
unloadChunk(chunk);
}
/** /**
* Gets the loaded {@link Chunk} at a position. * Gets the loaded {@link Chunk} at a position.
* <p> * <p>
@ -203,31 +235,29 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
* @param chunkZ the chunk Z * @param chunkZ the chunk Z
* @return the chunk at the specified position, null if not loaded * @return the chunk at the specified position, null if not loaded
*/ */
@Nullable public abstract @Nullable Chunk getChunk(int chunkX, int chunkZ);
public abstract Chunk getChunk(int chunkX, int chunkZ);
/** /**
* Saves a {@link Chunk} to permanent storage. * Saves a {@link Chunk} to permanent storage.
* *
* @param chunk the {@link Chunk} to save * @param chunk the {@link Chunk} to save
* @param callback optional callback called when the {@link Chunk} is done saving * @return future called when the chunk is done saving
*/ */
public abstract void saveChunkToStorage(@NotNull Chunk chunk, @Nullable Runnable callback); public abstract @NotNull CompletableFuture<Void> saveChunkToStorage(@NotNull Chunk chunk);
/** /**
* Saves multiple chunks to permanent storage. * Saves multiple chunks to permanent storage.
* *
* @param callback optional callback called when the chunks are done saving * @return future called when the chunks are done saving
*/ */
public abstract void saveChunksToStorage(@Nullable Runnable callback); public abstract @NotNull CompletableFuture<Void> saveChunksToStorage();
/** /**
* Gets the instance {@link ChunkGenerator}. * Gets the instance {@link ChunkGenerator}.
* *
* @return the {@link ChunkGenerator} of the instance * @return the {@link ChunkGenerator} of the instance
*/ */
@Nullable public abstract @Nullable ChunkGenerator getChunkGenerator();
public abstract ChunkGenerator getChunkGenerator();
/** /**
* Changes the instance {@link ChunkGenerator}. * Changes the instance {@link ChunkGenerator}.
@ -241,8 +271,7 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
* *
* @return an unmodifiable containing all the instance chunks * @return an unmodifiable containing all the instance chunks
*/ */
@NotNull public abstract @NotNull Collection<@NotNull Chunk> getChunks();
public abstract Collection<Chunk> getChunks();
/** /**
* Gets the instance {@link StorageLocation}. * Gets the instance {@link StorageLocation}.
@ -259,35 +288,6 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
*/ */
public abstract void setStorageLocation(@Nullable StorageLocation storageLocation); 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.
* <p>
* Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Chunk)} and to cache
* that this chunk has been loaded.
* <p>
* 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.
* <p>
* Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Chunk)} and to cache
* that this chunk has been loaded.
* <p>
* 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. * When set to true, chunks will load automatically when requested.
* Otherwise using {@link #loadChunk(int, int)} will be required to even spawn a player * Otherwise using {@link #loadChunk(int, int)} will be required to even spawn a player
@ -519,55 +519,6 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
return Collections.unmodifiableSet(entities); return Collections.unmodifiableSet(entities);
} }
/**
* Loads the {@link Chunk} at the given position without any callback.
* <p>
* 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) {
final int chunkX = ChunkUtils.getChunkCoordinate(point.x());
final int chunkZ = ChunkUtils.getChunkCoordinate(point.z());
loadChunk(chunkX, chunkZ, callback);
}
/**
* 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)
*/
public void loadOptionalChunk(@NotNull Point point, @Nullable ChunkCallback callback) {
final int chunkX = ChunkUtils.getChunkCoordinate(point.x());
final int chunkZ = ChunkUtils.getChunkCoordinate(point.z());
loadOptionalChunk(chunkX, chunkZ, callback);
}
/**
* Unloads the chunk at the given position.
*
* @param chunkX the chunk X
* @param chunkZ the chunk Z
*/
public void unloadChunk(int chunkX, int chunkZ) {
final Chunk chunk = getChunk(chunkX, chunkZ);
Check.notNull(chunk, "The chunk at " + chunkX + ":" + chunkZ + " is already unloaded");
unloadChunk(chunk);
}
@Override @Override
public @NotNull Block getBlock(int x, int y, int z) { public @NotNull Block getBlock(int x, int y, int z) {
final Chunk chunk = getChunkAt(x, z); final Chunk chunk = getChunkAt(x, z);
@ -622,33 +573,6 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
return getChunkAt(point.x(), point.z()); return getChunkAt(point.x(), point.z());
} }
/**
* Checks if the {@link Chunk} at the position is loaded.
*
* @param chunkX the chunk X
* @param chunkZ the chunk Z
* @return true if the chunk is loaded, false otherwise
*/
public boolean isChunkLoaded(int chunkX, int chunkZ) {
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. * Gets the instance unique id.
* *
@ -723,7 +647,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) // 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); Check.notNull(chunk, "You tried to spawn an entity in an unloaded chunk, " + entityPosition);
UNSAFE_addEntityToChunk(entity, chunk); UNSAFE_addEntityToChunk(entity, chunk);
}); });

View File

@ -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.EffectPacket;
import net.minestom.server.network.packet.server.play.UnloadChunkPacket; import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
import net.minestom.server.storage.StorageLocation; import net.minestom.server.storage.StorageLocation;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.PacketUtils; 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.ChunkSupplier;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
@ -32,11 +29,13 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
/** /**
* InstanceContainer is an instance that contains chunks in contrary to SharedInstance. * 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"); "Tried to set a block to an unloaded chunk with auto chunk load disabled");
final int chunkX = ChunkUtils.getChunkCoordinate(x); final int chunkX = ChunkUtils.getChunkCoordinate(x);
final int chunkZ = ChunkUtils.getChunkCoordinate(z); 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 @Override
public void loadChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { public @NotNull CompletableFuture<Chunk> loadChunk(int chunkX, int chunkZ) {
final Chunk chunk = getChunk(chunkX, chunkZ); final Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk != null) { if (chunk != null) {
// Chunk already loaded // Chunk already loaded
OptionalCallback.execute(callback, chunk); return CompletableFuture.completedFuture(chunk);
} else { } else {
// Retrieve chunk from somewhere else (file or create a new one using the ChunkGenerator) // Retrieve chunk from somewhere else (file or create a new one using the ChunkGenerator)
retrieveChunk(chunkX, chunkZ, callback); return retrieveChunk(chunkX, chunkZ);
} }
} }
@Override @Override
public void loadOptionalChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { public @NotNull CompletableFuture<Chunk> loadOptionalChunk(int chunkX, int chunkZ) {
final Chunk chunk = getChunk(chunkX, chunkZ); final Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk != null) { if (chunk != null) {
// Chunk already loaded // Chunk already loaded
OptionalCallback.execute(callback, chunk); return CompletableFuture.completedFuture(chunk);
} else { } else {
if (hasEnabledAutoChunkLoad()) { if (hasEnabledAutoChunkLoad()) {
// Use `IChunkLoader` or `ChunkGenerator` // Use `IChunkLoader` or `ChunkGenerator`
retrieveChunk(chunkX, chunkZ, callback); return retrieveChunk(chunkX, chunkZ);
} else { } else {
// Chunk not loaded, return null // 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()}.
* <p> * <p>
* WARNING: {@link #getData()} needs to be a {@link SerializableData} in order to be saved. * 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<Void> saveInstance() {
Check.notNull(getStorageLocation(), "You cannot save the instance if no StorageLocation has been defined"); Check.notNull(getStorageLocation(), "You cannot save the instance if no StorageLocation has been defined");
this.storageLocation.set(UUID_KEY, getUniqueId(), UUID.class); this.storageLocation.set(UUID_KEY, getUniqueId(), UUID.class);
@ -305,49 +302,48 @@ public class InstanceContainer extends Instance {
this.storageLocation.set(DATA_KEY, (SerializableData) getData(), SerializableData.class); this.storageLocation.set(DATA_KEY, (SerializableData) getData(), SerializableData.class);
} }
saveChunksToStorage(callback); return saveChunksToStorage();
}
/**
* Saves the instance without callback.
*
* @see #saveInstance(Runnable)
*/
public void saveInstance() {
saveInstance(null);
} }
@Override @Override
public void saveChunkToStorage(@NotNull Chunk chunk, Runnable callback) { public @NotNull CompletableFuture<Void> saveChunkToStorage(@NotNull Chunk chunk) {
this.chunkLoader.saveChunk(chunk, callback); return chunkLoader.saveChunk(chunk);
} }
@Override @Override
public void saveChunksToStorage(@Nullable Runnable callback) { public @NotNull CompletableFuture<Void> saveChunksToStorage() {
Collection<Chunk> chunksCollection = chunks.values(); Collection<Chunk> chunksCollection = chunks.values();
this.chunkLoader.saveChunks(chunksCollection, callback); return chunkLoader.saveChunks(chunksCollection);
} }
@Override protected @NotNull CompletableFuture<@NotNull Chunk> retrieveChunk(int chunkX, int chunkZ) {
protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> { final Runnable loader = () -> chunkLoader.loadChunk(this, chunkX, chunkZ)
cacheChunk(chunk); .whenComplete((chunk, throwable) -> {
UPDATE_MANAGER.signalChunkLoad(chunk); if (chunk != null) {
// Execute callback and event in the instance thread // Successfully loaded
scheduleNextTick(instance -> { cacheChunk(chunk);
callChunkLoadEvent(chunkX, chunkZ); UPDATE_MANAGER.signalChunkLoad(chunk);
OptionalCallback.execute(callback, chunk); // Execute callback and event in the instance thread
}); scheduleNextTick(instance -> {
}); callChunkLoadEvent(chunkX, chunkZ);
completableFuture.complete(chunk);
if (!loaded) { });
// Not found, create a new chunk } else {
createChunk(chunkX, chunkZ, callback); // Not present
createChunk(chunkX, chunkZ).thenAccept(completableFuture::complete);
}
});
if (chunkLoader.supportsParallelLoading()) {
CompletableFuture.runAsync(loader);
} else {
loader.run();
} }
// Chunk is being loaded
return completableFuture;
} }
@Override protected @NotNull CompletableFuture<@NotNull Chunk> createChunk(int chunkX, int chunkZ) {
protected void createChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) {
Biome[] biomes = new Biome[Biome.getBiomeCount(getDimensionType())]; Biome[] biomes = new Biome[Biome.getBiomeCount(getDimensionType())];
if (chunkGenerator == null) { if (chunkGenerator == null) {
Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0)); Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0));
@ -360,18 +356,21 @@ public class InstanceContainer extends Instance {
cacheChunk(chunk); cacheChunk(chunk);
final Consumer<Chunk> chunkRegisterCallback = (c) -> {
UPDATE_MANAGER.signalChunkLoad(c);
callChunkLoadEvent(chunkX, chunkZ);
};
if (chunkGenerator != null && chunk.shouldGenerate()) { if (chunkGenerator != null && chunk.shouldGenerate()) {
// Execute the chunk generator to populate the chunk // Execute the chunk generator to populate the chunk
final ChunkGenerationBatch chunkBatch = new ChunkGenerationBatch(this, chunk); final ChunkGenerationBatch chunkBatch = new ChunkGenerationBatch(this, chunk);
return chunkBatch.generate(chunkGenerator)
chunkBatch.generate(chunkGenerator, callback); .whenComplete((c, t) -> chunkRegisterCallback.accept(c));
} else { } else {
// No chunk generator, execute the callback with the empty chunk // No chunk generator, execute the callback with the empty chunk
OptionalCallback.execute(callback, chunk); chunkRegisterCallback.accept(chunk);
return CompletableFuture.completedFuture(chunk);
} }
UPDATE_MANAGER.signalChunkLoad(chunk);
callChunkLoadEvent(chunkX, chunkZ);
} }
@Override @Override
@ -396,7 +395,7 @@ public class InstanceContainer extends Instance {
* Uses {@link DynamicChunk} by default. * Uses {@link DynamicChunk} by default.
* <p> * <p>
* WARNING: if you need to save this instance's chunks later, * 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. * 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 * @param chunkSupplier the new {@link ChunkSupplier} of this instance, chunks need to be non-null

View File

@ -1,16 +1,17 @@
package net.minestom.server.instance; package net.minestom.server.instance;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockFace; import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.storage.StorageLocation; import net.minestom.server.storage.StorageLocation;
import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkCallback;
import net.minestom.server.coordinate.Point;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; 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}, * 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 @Override
public void loadChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { public @NotNull CompletableFuture<Chunk> loadChunk(int chunkX, int chunkZ) {
this.instanceContainer.loadChunk(chunkX, chunkZ, callback); return instanceContainer.loadChunk(chunkX, chunkZ);
} }
@Override @Override
public void loadOptionalChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { public @NotNull CompletableFuture<Chunk> loadOptionalChunk(int chunkX, int chunkZ) {
this.instanceContainer.loadOptionalChunk(chunkX, chunkZ, callback); return instanceContainer.loadOptionalChunk(chunkX, chunkZ);
} }
@Override @Override
@ -62,13 +63,13 @@ public class SharedInstance extends Instance {
} }
@Override @Override
public void saveChunkToStorage(@NotNull Chunk chunk, @Nullable Runnable callback) { public @NotNull CompletableFuture<Void> saveChunkToStorage(@NotNull Chunk chunk) {
this.instanceContainer.saveChunkToStorage(chunk, callback); return instanceContainer.saveChunkToStorage(chunk);
} }
@Override @Override
public void saveChunksToStorage(@Nullable Runnable callback) { public @NotNull CompletableFuture<Void> saveChunksToStorage() {
instanceContainer.saveChunksToStorage(callback); return instanceContainer.saveChunksToStorage();
} }
@Override @Override
@ -97,16 +98,6 @@ public class SharedInstance extends Instance {
this.instanceContainer.setStorageLocation(storageLocation); 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 @Override
public void enableAutoChunkLoad(boolean enable) { public void enableAutoChunkLoad(boolean enable) {
instanceContainer.enableAutoChunkLoad(enable); instanceContainer.enableAutoChunkLoad(enable);

View File

@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ChunkGenerationBatch extends ChunkBatch { public class ChunkGenerationBatch extends ChunkBatch {
private final InstanceContainer instance; private final InstanceContainer instance;
@ -23,7 +24,8 @@ public class ChunkGenerationBatch extends ChunkBatch {
chunk.setBlock(x, y, z, block); chunk.setBlock(x, y, z, block);
} }
public void generate(@NotNull ChunkGenerator chunkGenerator, @Nullable ChunkCallback callback) { public @NotNull CompletableFuture<@NotNull Chunk> generate(@NotNull ChunkGenerator chunkGenerator) {
final CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
BLOCK_BATCH_POOL.execute(() -> { BLOCK_BATCH_POOL.execute(() -> {
synchronized (chunk) { synchronized (chunk) {
final List<ChunkPopulator> populators = chunkGenerator.getPopulators(); final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
@ -40,10 +42,10 @@ public class ChunkGenerationBatch extends ChunkBatch {
// Update the chunk. // Update the chunk.
this.chunk.sendChunk(); this.chunk.sendChunk();
this.instance.refreshLastBlockChangeTime(); this.instance.refreshLastBlockChangeTime();
if (callback != null) completableFuture.complete(chunk);
this.instance.scheduleNextTick(inst -> callback.accept(this.chunk));
} }
}); });
return completableFuture;
} }
@Override @Override

View File

@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public final class AsyncUtils { public final class AsyncUtils {
public static final CompletableFuture<Void> NULL_FUTURE = CompletableFuture.completedFuture(null);
public static @NotNull CompletableFuture<Void> runAsync(@NotNull Runnable runnable) { public static @NotNull CompletableFuture<Void> runAsync(@NotNull Runnable runnable) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
@ -16,5 +17,4 @@ public final class AsyncUtils {
} }
}); });
} }
} }

View File

@ -4,12 +4,12 @@ import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.callback.OptionalCallback;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public final class ChunkUtils { 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 * 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. * {@code endCallback} when all the chunks in the array have been loaded.
* <p> * <p>
* 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. * if {@link Instance#hasEnabledAutoChunkLoad()} returns false and the chunk is not already loaded.
* *
* @param instance the instance to load the chunks from * @param instance the instance to load the chunks from
* @param chunks the chunks to loaded, long value from {@link #getChunkIndex(int, int)} * @param chunks the chunks to loaded, long value from {@link #getChunkIndex(int, int)}
* @param eachCallback the optional callback when a chunk get loaded * @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, public static @NotNull CompletableFuture<@Nullable Chunk> optionalLoadAll(@NotNull Instance instance, long @NotNull [] chunks,
@Nullable ChunkCallback eachCallback, @Nullable ChunkCallback endCallback) { @Nullable ChunkCallback eachCallback) {
CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
final int length = chunks.length; final int length = chunks.length;
AtomicInteger counter = new AtomicInteger(0); AtomicInteger counter = new AtomicInteger(0);
for (long visibleChunk : chunks) { for (long visibleChunk : chunks) {
final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk); final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk);
final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk); final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk);
@ -45,16 +45,17 @@ public final class ChunkUtils {
final boolean isLast = counter.get() == length - 1; final boolean isLast = counter.get() == length - 1;
if (isLast) { if (isLast) {
// This is the last chunk to be loaded , spawn player // This is the last chunk to be loaded , spawn player
OptionalCallback.execute(endCallback, chunk); completableFuture.complete(chunk);
} else { } else {
// Increment the counter of current loaded chunks // Increment the counter of current loaded chunks
counter.incrementAndGet(); counter.incrementAndGet();
} }
}; };
// WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck. // WARNING: if auto-load is disabled and no chunks are loaded beforehand, player will be stuck.
instance.loadOptionalChunk(chunkX, chunkZ, callback); instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(callback);
} }
return completableFuture;
} }
/** /**