Initial future commit

This commit is contained in:
TheMode 2021-07-11 02:54:02 +02:00
parent 560b450b3e
commit b9679bc1ac
9 changed files with 138 additions and 221 deletions

View File

@ -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<EntityEvent>, 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<Void> 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<Void> 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<Void> teleport(@NotNull Pos position) {
return teleport(position, null);
}
/**

View File

@ -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;

View File

@ -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<Void> 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) {

View File

@ -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<Void> saveChunk(@NotNull Chunk chunk);
/**
* Saves multiple chunks with an optional callback for when it is done.
* <p>
* 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<Chunk> chunks, @Nullable Runnable callback) {
default @NotNull CompletableFuture<Void> saveChunks(@NotNull Collection<Chunk> 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<Void> 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;
}
}

View File

@ -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<Chunk> 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<Chunk> 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<Void> 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<Void> 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.
* <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.
* 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.
* <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) {
public CompletableFuture<Chunk> 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<Chunk> 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);
});

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.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<Chunk> 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<Chunk> 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()}.
* <p>
* 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");
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<Void> saveChunkToStorage(@NotNull Chunk chunk) {
return chunkLoader.saveChunk(chunk);
}
@Override
public void saveChunksToStorage(@Nullable Runnable callback) {
public CompletableFuture<Void> saveChunksToStorage() {
Collection<Chunk> 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<Chunk> retrieveChunk(int chunkX, int chunkZ) {
CompletableFuture<Chunk> 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<Chunk> 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<Chunk> 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.
* <p>
* 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

View File

@ -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<Chunk> 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<Chunk> 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<Void> saveChunkToStorage(@NotNull Chunk chunk) {
return instanceContainer.saveChunkToStorage(chunk);
}
@Override
public void saveChunksToStorage(@Nullable Runnable callback) {
instanceContainer.saveChunksToStorage(callback);
public CompletableFuture<Void> 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);

View File

@ -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<Chunk> generate(@NotNull ChunkGenerator chunkGenerator) {
final CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
BLOCK_BATCH_POOL.execute(() -> {
synchronized (chunk) {
final List<ChunkPopulator> 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

View File

@ -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.
* <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.
*
* @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<Chunk> optionalLoadAll(@NotNull Instance instance, long @NotNull [] chunks,
@Nullable ChunkCallback eachCallback) {
CompletableFuture<Chunk> 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;
}
/**