mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-02 14:38:26 +01:00
Initial future commit
This commit is contained in:
parent
560b450b3e
commit
b9679bc1ac
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user