Simplify InstanceContainer

This commit is contained in:
TheMode 2021-08-15 05:58:53 +02:00
parent af50bbb440
commit 6c242cbc7f
2 changed files with 65 additions and 157 deletions

View File

@ -20,6 +20,7 @@ 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.PacketUtils; import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
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,9 +33,8 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
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.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier;
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.
@ -50,7 +50,7 @@ public class InstanceContainer extends Instance {
// used as a monitor when access is required // used as a monitor when access is required
private final Long2ObjectMap<Chunk> chunks = new Long2ObjectOpenHashMap<>(); private final Long2ObjectMap<Chunk> chunks = new Long2ObjectOpenHashMap<>();
private final ReadWriteLock changingBlockLock = new ReentrantReadWriteLock(); private final Lock changingBlockLock = new ReentrantLock();
private final Map<Point, Block> currentlyChangingBlocks = new HashMap<>(); private final Map<Point, Block> currentlyChangingBlocks = new HashMap<>();
// the chunk loader, used when trying to load/save a chunk from another source // the chunk loader, used when trying to load/save a chunk from another source
@ -74,10 +74,8 @@ public class InstanceContainer extends Instance {
*/ */
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType) { public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType) {
super(uniqueId, dimensionType); super(uniqueId, dimensionType);
// Set the default chunk supplier using DynamicChunk // Set the default chunk supplier using DynamicChunk
setChunkSupplier(DynamicChunk::new); setChunkSupplier(DynamicChunk::new);
// Set the default chunk loader which use the Anvil format // Set the default chunk loader which use the Anvil format
setChunkLoader(new AnvilLoader("world")); setChunkLoader(new AnvilLoader("world"));
this.chunkLoader.loadInstance(this); this.chunkLoader.loadInstance(this);
@ -110,10 +108,7 @@ public class InstanceContainer extends Instance {
*/ */
private void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block, private void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block,
@Nullable BlockHandler.Placement placement, @Nullable BlockHandler.Destroy destroy) { @Nullable BlockHandler.Placement placement, @Nullable BlockHandler.Destroy destroy) {
// Cannot place block in a read-only chunk if (chunk.isReadOnly()) return;
if (chunk.isReadOnly()) {
return;
}
synchronized (chunk) { synchronized (chunk) {
// Refresh the last block change time // Refresh the last block change time
this.lastBlockChangeTime = System.currentTimeMillis(); this.lastBlockChangeTime = System.currentTimeMillis();
@ -123,13 +118,16 @@ public class InstanceContainer extends Instance {
// This can happen with nether portals which break the entire frame when a portal block is broken // This can happen with nether portals which break the entire frame when a portal block is broken
return; return;
} }
setAlreadyChanged(blockPosition, block); this.currentlyChangingBlocks.put(blockPosition, block);
final Block previousBlock = chunk.getBlock(blockPosition); final Block previousBlock = chunk.getBlock(blockPosition);
final BlockHandler previousHandler = previousBlock.handler(); final BlockHandler previousHandler = previousBlock.handler();
// Change id based on neighbors // Change id based on neighbors
block = executeBlockPlacementRule(block, blockPosition); final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block);
if (blockPlacementRule != null) {
block = blockPlacementRule.blockUpdate(this, blockPosition, block);
}
// Set the block // Set the block
chunk.setBlock(x, y, z, block); chunk.setBlock(x, y, z, block);
@ -138,7 +136,7 @@ public class InstanceContainer extends Instance {
executeNeighboursBlockPlacementRule(blockPosition); executeNeighboursBlockPlacementRule(blockPosition);
// Refresh player chunk block // Refresh player chunk block
sendBlockChange(chunk, blockPosition, block); chunk.sendPacketToViewers(new BlockChangePacket(blockPosition, block.stateId()));
if (previousHandler != null) { if (previousHandler != null) {
// Previous destroy // Previous destroy
@ -159,8 +157,7 @@ public class InstanceContainer extends Instance {
public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull Point blockPosition, public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull Point blockPosition,
@NotNull BlockFace blockFace, float cursorX, float cursorY, float cursorZ) { @NotNull BlockFace blockFace, float cursorX, float cursorY, float cursorZ) {
final Chunk chunk = getChunkAt(blockPosition); final Chunk chunk = getChunkAt(blockPosition);
if (!ChunkUtils.isLoaded(chunk)) if (!ChunkUtils.isLoaded(chunk)) return false;
return false;
UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), block, UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), block,
new BlockHandler.PlayerPlacement(block, this, blockPosition, player, blockFace, cursorX, cursorY, cursorZ), null); new BlockHandler.PlayerPlacement(block, this, blockPosition, player, blockFace, cursorX, cursorY, cursorZ), null);
return true; return true;
@ -170,24 +167,18 @@ public class InstanceContainer extends Instance {
public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition) { public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition) {
final Chunk chunk = getChunkAt(blockPosition); final Chunk chunk = getChunkAt(blockPosition);
Check.notNull(chunk, "You cannot break blocks in a null chunk!"); Check.notNull(chunk, "You cannot break blocks in a null chunk!");
// Cancel if the chunk is read-only if (chunk.isReadOnly()) return false;
if (chunk.isReadOnly()) { if (!ChunkUtils.isLoaded(chunk)) return false;
return false;
}
// Chunk unloaded, stop here
if (!ChunkUtils.isLoaded(chunk))
return false;
final Block block = getBlock(blockPosition);
final Block block = getBlock(blockPosition);
final int x = blockPosition.blockX(); final int x = blockPosition.blockX();
final int y = blockPosition.blockY(); final int y = blockPosition.blockY();
final int z = blockPosition.blockZ(); final int z = blockPosition.blockZ();
// The player probably have a wrong version of this chunk section, send it
if (block.isAir()) { if (block.isAir()) {
// The player probably have a wrong version of this chunk section, send it
chunk.sendChunk(player); chunk.sendChunk(player);
return false; return false;
} }
PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(player, block, Block.AIR, blockPosition); PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(player, block, Block.AIR, blockPosition);
EventDispatcher.call(blockBreakEvent); EventDispatcher.call(blockBreakEvent);
final boolean allowed = !blockBreakEvent.isCancelled(); final boolean allowed = !blockBreakEvent.isCancelled();
@ -207,31 +198,12 @@ public class InstanceContainer extends Instance {
@Override @Override
public @NotNull CompletableFuture<Chunk> loadChunk(int chunkX, int chunkZ) { public @NotNull CompletableFuture<Chunk> loadChunk(int chunkX, int chunkZ) {
final Chunk chunk = getChunk(chunkX, chunkZ); return loadOrRetrieve(chunkX, chunkZ, () -> retrieveChunk(chunkX, chunkZ));
if (chunk != null) {
// Chunk already loaded
return CompletableFuture.completedFuture(chunk);
} else {
// Retrieve chunk from somewhere else (file or create a new one using the ChunkGenerator)
return retrieveChunk(chunkX, chunkZ);
}
} }
@Override @Override
public @NotNull CompletableFuture<Chunk> loadOptionalChunk(int chunkX, int chunkZ) { public @NotNull CompletableFuture<Chunk> loadOptionalChunk(int chunkX, int chunkZ) {
final Chunk chunk = getChunk(chunkX, chunkZ); return loadOrRetrieve(chunkX, chunkZ, () -> hasEnabledAutoChunkLoad() ? retrieveChunk(chunkX, chunkZ) : AsyncUtils.empty());
if (chunk != null) {
// Chunk already loaded
return CompletableFuture.completedFuture(chunk);
} else {
if (hasEnabledAutoChunkLoad()) {
// Use `IChunkLoader` or `ChunkGenerator`
return retrieveChunk(chunkX, chunkZ);
} else {
// Chunk not loaded, return null
return CompletableFuture.completedFuture(null);
}
}
} }
@Override @Override
@ -246,7 +218,7 @@ public class InstanceContainer extends Instance {
chunk.removeViewer(viewer); chunk.removeViewer(viewer);
} }
callChunkUnloadEvent(chunkX, chunkZ); EventDispatcher.call(new InstanceChunkUnloadEvent(this, chunkX, chunkZ));
// Remove all entities in chunk // Remove all entities in chunk
getChunkEntities(chunk).forEach(entity -> { getChunkEntities(chunk).forEach(entity -> {
if (!(entity instanceof Player)) entity.remove(); if (!(entity instanceof Player)) entity.remove();
@ -287,57 +259,38 @@ public class InstanceContainer extends Instance {
protected @NotNull CompletableFuture<@NotNull Chunk> retrieveChunk(int chunkX, int chunkZ) { protected @NotNull CompletableFuture<@NotNull Chunk> retrieveChunk(int chunkX, int chunkZ) {
CompletableFuture<Chunk> completableFuture = new CompletableFuture<>(); CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
final Runnable loader = () -> chunkLoader.loadChunk(this, chunkX, chunkZ) final IChunkLoader loader = chunkLoader;
.whenComplete((chunk, throwable) -> { final Runnable retriever = () -> loader.loadChunk(this, chunkX, chunkZ)
if (chunk != null) { .thenCompose(chunk -> chunk != null ? CompletableFuture.completedFuture(chunk) : createChunk(chunkX, chunkZ))
// Successfully loaded .whenComplete((chunk, throwable) -> scheduleNextTick(instance -> {
cacheChunk(chunk); cacheChunk(chunk);
UPDATE_MANAGER.signalChunkLoad(chunk); EventDispatcher.call(new InstanceChunkLoadEvent(this, chunkX, chunkZ));
// Execute callback and event in the instance thread completableFuture.complete(chunk);
scheduleNextTick(instance -> { }));
callChunkLoadEvent(chunkX, chunkZ); if (loader.supportsParallelLoading()) {
completableFuture.complete(chunk); CompletableFuture.runAsync(retriever);
});
} else {
// Not present
createChunk(chunkX, chunkZ).thenAccept(completableFuture::complete);
}
});
if (chunkLoader.supportsParallelLoading()) {
CompletableFuture.runAsync(loader);
} else { } else {
loader.run(); retriever.run();
} }
// Chunk is being loaded
return completableFuture; return completableFuture;
} }
protected @NotNull CompletableFuture<@NotNull Chunk> createChunk(int chunkX, int chunkZ) { protected @NotNull CompletableFuture<@NotNull Chunk> createChunk(int chunkX, int chunkZ) {
final ChunkGenerator generator = this.chunkGenerator;
Biome[] biomes = new Biome[Biome.getBiomeCount(getDimensionType())]; Biome[] biomes = new Biome[Biome.getBiomeCount(getDimensionType())];
if (chunkGenerator == null) { if (generator == null) {
Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0)); Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0));
} else { } else {
chunkGenerator.fillBiomes(biomes, chunkX, chunkZ); generator.fillBiomes(biomes, chunkX, chunkZ);
} }
final Chunk chunk = chunkSupplier.createChunk(this, biomes, chunkX, chunkZ); final Chunk chunk = chunkSupplier.createChunk(this, biomes, chunkX, chunkZ);
Check.notNull(chunk, "Chunks supplied by a ChunkSupplier cannot be null."); Check.notNull(chunk, "Chunks supplied by a ChunkSupplier cannot be null.");
if (generator != null && chunk.shouldGenerate()) {
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 // 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) return chunkBatch.generate(generator);
.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
chunkRegisterCallback.accept(chunk);
return CompletableFuture.completedFuture(chunk); return CompletableFuture.completedFuture(chunk);
} }
} }
@ -431,11 +384,8 @@ public class InstanceContainer extends Instance {
for (Chunk chunk : chunks.values()) { for (Chunk chunk : chunks.values()) {
final int chunkX = chunk.getChunkX(); final int chunkX = chunk.getChunkX();
final int chunkZ = chunk.getChunkZ(); final int chunkZ = chunk.getChunkZ();
final Chunk copiedChunk = chunk.copy(copiedInstance, chunkX, chunkZ); final Chunk copiedChunk = chunk.copy(copiedInstance, chunkX, chunkZ);
copiedInstance.cacheChunk(copiedChunk); copiedInstance.cacheChunk(copiedChunk);
UPDATE_MANAGER.signalChunkLoad(copiedChunk);
} }
} }
return copiedInstance; return copiedInstance;
@ -449,8 +399,7 @@ public class InstanceContainer extends Instance {
* @return the instance source, null if not created by a copy * @return the instance source, null if not created by a copy
* @see #copy() to create a copy of this instance with 'this' as the source * @see #copy() to create a copy of this instance with 'this' as the source
*/ */
@Nullable public @Nullable InstanceContainer getSrcInstance() {
public InstanceContainer getSrcInstance() {
return srcInstance; return srcInstance;
} }
@ -472,21 +421,6 @@ public class InstanceContainer extends Instance {
this.lastBlockChangeTime = System.currentTimeMillis(); this.lastBlockChangeTime = System.currentTimeMillis();
} }
/**
* Adds a {@link Chunk} to the internal instance map.
* <p>
* WARNING: the chunk will not automatically be sent to players and
* {@link net.minestom.server.UpdateManager#signalChunkLoad(Chunk)} must be called manually.
*
* @param chunk the chunk to cache
*/
public void cacheChunk(@NotNull Chunk chunk) {
final long index = ChunkUtils.getChunkIndex(chunk);
synchronized (chunks) {
this.chunks.put(index, chunk);
}
}
@Override @Override
public ChunkGenerator getChunkGenerator() { public ChunkGenerator getChunkGenerator() {
return chunkGenerator; return chunkGenerator;
@ -527,34 +461,17 @@ public class InstanceContainer extends Instance {
this.chunkLoader = chunkLoader; this.chunkLoader = chunkLoader;
} }
/**
* Sends a {@link BlockChangePacket} at the specified position to set the block as {@code blockStateId}.
* <p>
* WARNING: this does not change the internal block data, this is strictly visual for the players.
*
* @param chunk the chunk where the block is
* @param blockPosition the block position
* @param block the new block
*/
private void sendBlockChange(@NotNull Chunk chunk, @NotNull Point blockPosition, @NotNull Block block) {
chunk.sendPacketToViewers(new BlockChangePacket(blockPosition, block.stateId()));
}
@Override @Override
public void tick(long time) { public void tick(long time) {
// Time/world border // Time/world border
super.tick(time); super.tick(time);
// Clear block change map
Lock wrlock = changingBlockLock.writeLock(); Lock wrlock = this.changingBlockLock;
wrlock.lock(); wrlock.lock();
currentlyChangingBlocks.clear(); this.currentlyChangingBlocks.clear();
wrlock.unlock(); wrlock.unlock();
} }
private void setAlreadyChanged(@NotNull Point blockPosition, Block block) {
currentlyChangingBlocks.put(blockPosition, block);
}
/** /**
* Has this block already changed since last update? * Has this block already changed since last update?
* Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace. * Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace.
@ -565,24 +482,7 @@ public class InstanceContainer extends Instance {
*/ */
private boolean isAlreadyChanged(@NotNull Point blockPosition, @NotNull Block block) { private boolean isAlreadyChanged(@NotNull Point blockPosition, @NotNull Block block) {
final Block changedBlock = currentlyChangingBlocks.get(blockPosition); final Block changedBlock = currentlyChangingBlocks.get(blockPosition);
if (changedBlock == null) return changedBlock != null && changedBlock.id() == block.id();
return false;
return changedBlock.id() == block.id();
}
/**
* Calls the {@link BlockPlacementRule} for the specified block state id.
*
* @param block the block to modify
* @param blockPosition the block position
* @return the modified block state id
*/
private Block executeBlockPlacementRule(Block block, @NotNull Point blockPosition) {
final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block);
if (blockPlacementRule != null) {
return blockPlacementRule.blockUpdate(this, blockPosition, block);
}
return block;
} }
/** /**
@ -602,33 +502,37 @@ public class InstanceContainer extends Instance {
final int neighborY = blockPosition.blockY() + offsetY; final int neighborY = blockPosition.blockY() + offsetY;
final int neighborZ = blockPosition.blockZ() + offsetZ; final int neighborZ = blockPosition.blockZ() + offsetZ;
final Chunk chunk = getChunkAt(neighborX, neighborZ); final Chunk chunk = getChunkAt(neighborX, neighborZ);
if (chunk == null) continue;
// Do not try to get neighbour in an unloaded chunk
if (chunk == null)
continue;
final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ); final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ);
final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock); final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock);
if (neighborBlockPlacementRule != null) { if (neighborBlockPlacementRule == null) continue;
final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ);
final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this, final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ);
neighborPosition, neighborBlock); final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this,
if (neighborBlock != newNeighborBlock) { neighborPosition, neighborBlock);
setBlock(neighborPosition, newNeighborBlock); if (neighborBlock != newNeighborBlock) {
} setBlock(neighborPosition, newNeighborBlock);
} }
} }
} }
} }
} }
private void callChunkLoadEvent(int chunkX, int chunkZ) { private CompletableFuture<Chunk> loadOrRetrieve(int chunkX, int chunkZ, Supplier<CompletableFuture<Chunk>> supplier) {
InstanceChunkLoadEvent chunkLoadEvent = new InstanceChunkLoadEvent(this, chunkX, chunkZ); final Chunk chunk = getChunk(chunkX, chunkZ);
EventDispatcher.call(chunkLoadEvent); if (chunk != null) {
// Chunk already loaded
return CompletableFuture.completedFuture(chunk);
}
return supplier.get();
} }
private void callChunkUnloadEvent(int chunkX, int chunkZ) { private void cacheChunk(@NotNull Chunk chunk) {
InstanceChunkUnloadEvent chunkUnloadEvent = new InstanceChunkUnloadEvent(this, chunkX, chunkZ); final long index = ChunkUtils.getChunkIndex(chunk);
EventDispatcher.call(chunkUnloadEvent); synchronized (chunks) {
this.chunks.put(index, chunk);
}
UPDATE_MANAGER.signalChunkLoad(chunk);
} }
} }

View File

@ -10,6 +10,10 @@ import java.util.concurrent.CompletableFuture;
public final class AsyncUtils { public final class AsyncUtils {
public static final CompletableFuture<Void> VOID_FUTURE = CompletableFuture.completedFuture(null); public static final CompletableFuture<Void> VOID_FUTURE = CompletableFuture.completedFuture(null);
public static <T> CompletableFuture<T> empty() {
return 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(() -> {
try { try {