Added IChunkLoader#saveChunks with a default implementation

This commit is contained in:
themode 2020-10-24 09:34:19 +02:00
parent 4ddfc88d43
commit 3e59c9d396
3 changed files with 67 additions and 43 deletions

View File

@ -1,6 +1,13 @@
package net.minestom.server.instance; 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.chunk.ChunkCallback;
import net.minestom.server.utils.thread.MinestomThread;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Interface implemented to change the way chunks are loaded/saved. * Interface implemented to change the way chunks are loaded/saved.
@ -10,26 +17,61 @@ import net.minestom.server.utils.chunk.ChunkCallback;
public interface IChunkLoader { public interface IChunkLoader {
/** /**
* Load a {@link Chunk}, all blocks should be set since the {@link ChunkGenerator} is not applied * Loads a {@link Chunk}, all blocks should be set since the {@link ChunkGenerator} is not applied.
* *
* @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, * @param callback the callback executed when the {@link Chunk} is done loading,
* never called if the method returns false. * never called if the method returns false. Can be null.
* @return true if the chunk loaded successfully, false otherwise * @return true if the chunk loaded successfully, false otherwise
*/ */
boolean loadChunk(Instance instance, int chunkX, int chunkZ, ChunkCallback callback); boolean loadChunk(Instance instance, int chunkX, int chunkZ, ChunkCallback callback);
/** /**
* Save a {@link Chunk} with a 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, * @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) * should be called even if the saving failed (you can throw an exception).
* Can be null.
*/ */
void saveChunk(Chunk chunk, Runnable callback); void saveChunk(Chunk chunk, Runnable callback);
/**
* 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.
*/
default void saveChunks(Collection<Chunk> chunks, Runnable callback) {
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)));
try {
parallelSavingThreadPool.shutdown();
parallelSavingThreadPool.awaitTermination(1L, java.util.concurrent.TimeUnit.DAYS);
OptionalCallback.execute(callback);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
AtomicInteger counter = new AtomicInteger();
for (Chunk chunk : chunks) {
saveChunk(chunk, () -> {
final boolean isLast = counter.incrementAndGet() == chunks.size();
if (isLast) {
OptionalCallback.execute(callback);
}
});
}
}
}
/** /**
* Does this {@link IChunkLoader} allow for multi-threaded saving of {@link Chunk}? * Does this {@link IChunkLoader} allow for multi-threaded saving of {@link Chunk}?
* *

View File

@ -142,7 +142,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* *
* @param chunkX the chunk X * @param chunkX the chunk X
* @param chunkZ the chunk Z * @param chunkZ the chunk Z
* @param callback consumer called after the chunk has been generated, * @param callback optional consumer called after the chunk has been generated,
* the returned chunk will never be null * the returned chunk will never be null
*/ */
public abstract void loadChunk(int chunkX, int chunkZ, ChunkCallback callback); public abstract void loadChunk(int chunkX, int chunkZ, ChunkCallback callback);
@ -153,7 +153,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* *
* @param chunkX the chunk X * @param chunkX the chunk X
* @param chunkZ the chunk Z * @param chunkZ the chunk Z
* @param callback consumer called after the chunk has tried to be loaded, * @param callback optional consumer called after the chunk has tried to be loaded,
* contains a chunk if it is successful, null otherwise * contains a chunk if it is successful, null otherwise
*/ */
public abstract void loadOptionalChunk(int chunkX, int chunkZ, ChunkCallback callback); public abstract void loadOptionalChunk(int chunkX, int chunkZ, ChunkCallback callback);
@ -184,14 +184,14 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* 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 called when the {@link Chunk} is done saving * @param callback optional callback called when the {@link Chunk} is done saving
*/ */
public abstract void saveChunkToStorage(Chunk chunk, Runnable callback); public abstract void saveChunkToStorage(Chunk chunk, Runnable callback);
/** /**
* Saves multiple chunks to permanent storage. * Saves multiple chunks to permanent storage.
* *
* @param callback called when the chunks are done saving * @param callback optional callback called when the chunks are done saving
*/ */
public abstract void saveChunksToStorage(Runnable callback); public abstract void saveChunksToStorage(Runnable callback);
@ -254,7 +254,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* *
* @param chunkX the chunk X * @param chunkX the chunk X
* @param chunkZ the chunk X * @param chunkZ the chunk X
* @param callback the callback executed once the {@link Chunk} has been retrieved * @param callback the optional callback executed once the {@link Chunk} has been retrieved
*/ */
protected abstract void retrieveChunk(int chunkX, int chunkZ, ChunkCallback callback); protected abstract void retrieveChunk(int chunkX, int chunkZ, ChunkCallback callback);
@ -265,7 +265,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* *
* @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 with the newly created {@link Chunk} * @param callback the optional callback executed with the newly created {@link Chunk}
*/ */
protected abstract void createChunk(int chunkX, int chunkZ, ChunkCallback callback); protected abstract void createChunk(int chunkX, int chunkZ, ChunkCallback callback);
@ -541,7 +541,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* Loads the chunk at the given {@link Position} with a callback. * Loads the chunk at the given {@link Position} with a callback.
* *
* @param position the chunk position * @param position the chunk position
* @param callback the callback to run when the chunk is loaded * @param callback the optional callback to run when the chunk is loaded
*/ */
public void loadChunk(Position position, ChunkCallback callback) { public void loadChunk(Position position, ChunkCallback callback) {
final int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX()); final int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX());
@ -554,7 +554,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* at the given {@link Position} with a callback. * at the given {@link Position} with a callback.
* *
* @param position the chunk position * @param position the chunk position
* @param callback the callback executed when the chunk is loaded (or with a null chunk if not) * @param callback the optional callback executed when the chunk is loaded (or with a null chunk if not)
*/ */
public void loadOptionalChunk(Position position, ChunkCallback callback) { public void loadOptionalChunk(Position position, ChunkCallback callback) {
final int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX()); final int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX());
@ -933,7 +933,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* Schedules a block update at a given {@link BlockPosition}. * Schedules a block update at a given {@link BlockPosition}.
* Does nothing if no {@link CustomBlock} is present at 'position'. * Does nothing if no {@link CustomBlock} is present at 'position'.
* <p> * <p>
* Cancelled if the block changes between this call and the actual update * Cancelled if the block changes between this call and the actual update.
* *
* @param time in how long this update must be performed? * @param time in how long this update must be performed?
* @param unit in what unit is the time expressed * @param unit in what unit is the time expressed
@ -946,7 +946,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* <p> * <p>
* Warning: this does not update chunks and entities. * Warning: this does not update chunks and entities.
* *
* @param time the current time * @param time the tick time in milliseconds
*/ */
public void tick(long time) { public void tick(long time) {
// scheduled tasks // scheduled tasks

View File

@ -25,7 +25,6 @@ import net.minestom.server.utils.callback.OptionalCallback;
import net.minestom.server.utils.chunk.ChunkCallback; 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.thread.MinestomThread;
import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.DimensionType; import net.minestom.server.world.DimensionType;
@ -34,7 +33,6 @@ import net.minestom.server.world.biomes.Biome;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
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;
@ -221,11 +219,12 @@ public class InstanceContainer extends Instance {
} }
/** /**
* Has this block already changed since last update? Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace. * Has this block already changed since last update?
* Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace.
* *
* @param blockPosition the block position * @param blockPosition the block position
* @param blockStateId the block state id * @param blockStateId the block state id
* @return * @return true if the block changed since the last update
*/ */
private boolean isAlreadyChanged(BlockPosition blockPosition, short blockStateId) { private boolean isAlreadyChanged(BlockPosition blockPosition, short blockStateId) {
final Block changedBlock = currentlyChangingBlocks.get(blockPosition); final Block changedBlock = currentlyChangingBlocks.get(blockPosition);
@ -248,7 +247,7 @@ public class InstanceContainer extends Instance {
/** /**
* Calls {@link CustomBlock#onDestroy(Instance, BlockPosition, Data)} for {@code previousBlock}. * Calls {@link CustomBlock#onDestroy(Instance, BlockPosition, Data)} for {@code previousBlock}.
* <p> * <p>
* WARNING {@code chunk} needs to be synchronized * WARNING {@code chunk} needs to be synchronized.
* *
* @param chunk the chunk where the block is * @param chunk the chunk where the block is
* @param index the index of the block * @param index the index of the block
@ -263,7 +262,7 @@ public class InstanceContainer extends Instance {
/** /**
* Calls {@link CustomBlock#onPlace(Instance, BlockPosition, Data)} for the current custom block at the position. * Calls {@link CustomBlock#onPlace(Instance, BlockPosition, Data)} for the current custom block at the position.
* <p> * <p>
* WARNING {@code chunk} needs to be synchronized * WARNING {@code chunk} needs to be synchronized.
* *
* @param chunk the chunk where the block is * @param chunk the chunk where the block is
* @param index the block index * @param index the block index
@ -444,9 +443,9 @@ 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(Runnable)}.
* <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 callback once the saving is done * @param callback the callback once the saving is done. Can be null.
*/ */
public void saveInstance(Runnable callback) { public void saveInstance(Runnable callback) {
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");
@ -464,7 +463,7 @@ public class InstanceContainer extends Instance {
} }
/** /**
* Save the instance without callback * Save the instance without callback.
* *
* @see #saveInstance(Runnable) * @see #saveInstance(Runnable)
*/ */
@ -474,29 +473,12 @@ public class InstanceContainer extends Instance {
@Override @Override
public void saveChunkToStorage(Chunk chunk, Runnable callback) { public void saveChunkToStorage(Chunk chunk, Runnable callback) {
chunkLoader.saveChunk(chunk, callback); this.chunkLoader.saveChunk(chunk, callback);
} }
@Override @Override
public void saveChunksToStorage(Runnable callback) { public void saveChunksToStorage(Runnable callback) {
if (chunkLoader.supportsParallelSaving()) { this.chunkLoader.saveChunks(chunks.values(), callback);
ExecutorService parallelSavingThreadPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PARALLEL_CHUNK_SAVING, MinecraftServer.THREAD_NAME_PARALLEL_CHUNK_SAVING, true);
getChunks().forEach(c -> parallelSavingThreadPool.execute(() -> saveChunkToStorage(c, null)));
try {
parallelSavingThreadPool.shutdown();
parallelSavingThreadPool.awaitTermination(1L, java.util.concurrent.TimeUnit.DAYS);
OptionalCallback.execute(callback);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
final Iterator<Chunk> chunkIterator = chunks.values().iterator();
while (chunkIterator.hasNext()) {
final Chunk chunk = chunkIterator.next();
final boolean isLast = !chunkIterator.hasNext();
saveChunkToStorage(chunk, isLast ? callback : null);
}
}
} }
@Override @Override
@ -579,7 +561,7 @@ public class InstanceContainer extends Instance {
* <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, ChunkCallback)}
* to create the correct type of {@link Chunk}. tl;dr: Need chunk save = no random * 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
* @throws NullPointerException if {@code chunkSupplier} is null * @throws NullPointerException if {@code chunkSupplier} is null