Do not crash with a stack overflow when trying to change the block at 'blockPosition' in CustomBlock

This commit is contained in:
jglrxavpok 2020-05-04 21:06:13 +02:00
parent cad5dfb8eb
commit 8b41c7550b
4 changed files with 53 additions and 1 deletions

View File

@ -20,7 +20,12 @@ public class StoneBlock extends CustomBlock {
@Override
public void onDestroy(Instance instance, BlockPosition blockPosition, Data data) {
BlockPosition above = blockPosition.clone().add(0, 1, 0);
CustomBlock blockAbove = instance.getCustomBlock(above);
if(blockAbove == this) {
instance.setBlock(above, Block.AIR);
instance.setBlock(blockPosition, Block.AIR); // this should NOT create a stack overflow simply because we are trying to remove this same block
}
}
@Override

View File

@ -365,4 +365,10 @@ public abstract class Instance implements BlockModifier, DataContainer {
*/
public abstract void scheduleUpdate(int time, TimeUnit unit, BlockPosition position);
/**
* Performs a single tick in the instance.
* By default, does nothing
* @param time the current time
*/
public void tick(long time) { }
}

View File

@ -7,6 +7,7 @@ import net.minestom.server.entity.Player;
import net.minestom.server.event.PlayerBlockBreakEvent;
import net.minestom.server.instance.batch.BlockBatch;
import net.minestom.server.instance.batch.ChunkBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
import net.minestom.server.network.PacketWriterUtils;
@ -28,6 +29,9 @@ import net.minestom.server.world.Dimension;
import java.util.*;
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;
/**
@ -42,6 +46,9 @@ public class InstanceContainer extends Instance {
private ChunkGenerator chunkGenerator;
private Map<Long, Chunk> chunks = new ConcurrentHashMap<>();
private ReadWriteLock changingBlockLock = new ReentrantReadWriteLock();
private Map<BlockPosition, Block> currentlyChangingBlocks = new HashMap<>();
private boolean autoChunkLoad;
protected InstanceContainer(UUID uniqueId, Dimension dimension, StorageFolder storageFolder) {
@ -76,6 +83,13 @@ public class InstanceContainer extends Instance {
BlockPosition blockPosition = new BlockPosition(x, y, z);
if(isAlreadyChanged(blockPosition, blockId)) { // do NOT change the block again.
// Avoids StackOverflowExceptions when onDestroy tries to destroy the block itself
// This can happen with nether portals which break the entire frame when a portal block is broken
return;
}
setAlreadyChanged(blockPosition, blockId);
// Call the destroy listener if previous block was a custom block
callBlockDestroy(chunk, index, blockPosition);
@ -102,6 +116,23 @@ public class InstanceContainer extends Instance {
}
}
private void setAlreadyChanged(BlockPosition blockPosition, short blockId) {
currentlyChangingBlocks.put(blockPosition, Block.fromId(blockId));
}
/**
* Has this block already changed since last update? Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace
* @param blockPosition
* @param blockId
* @return
*/
private boolean isAlreadyChanged(BlockPosition blockPosition, short blockId) {
Block changedBlock = currentlyChangingBlocks.get(blockPosition);
if(changedBlock == null)
return false;
return changedBlock.getBlockId() == blockId;
}
@Override
public void refreshBlockId(BlockPosition blockPosition, short blockId) {
Chunk chunk = getChunkAt(blockPosition.getX(), blockPosition.getZ());
@ -416,4 +447,13 @@ public class InstanceContainer extends Instance {
}
}, new UpdateOption(time, unit));
}
@Override
public void tick(long time) {
super.tick(time);
Lock wrlock = changingBlockLock.writeLock();
wrlock.lock();
currentlyChangingBlocks.clear();
wrlock.unlock();
}
}

View File

@ -54,6 +54,7 @@ public class InstanceManager {
if (instance instanceof InstanceContainer) { // SharedInstance should be updated at the same time (verify?)
blocksPool.execute(() -> {
instance.tick(time);
for (Chunk chunk : instance.getChunks()) {
chunk.updateBlocks(time, instance);
}