Chunk update

This commit is contained in:
Felix Cravic 2020-08-16 00:53:42 +02:00
parent a7484f8644
commit caa89dd2f8
11 changed files with 388 additions and 396 deletions

View File

@ -39,7 +39,10 @@ import net.minestom.server.scoreboard.Team;
import net.minestom.server.sound.Sound;
import net.minestom.server.sound.SoundCategory;
import net.minestom.server.stat.PlayerStatistic;
import net.minestom.server.utils.*;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.DimensionType;
@ -540,7 +543,7 @@ public class Player extends LivingEntity implements CommandSender {
viewableChunks.clear();
if (this.instance != null) {
DimensionType instanceDimensionType = instance.getDimensionType();
final DimensionType instanceDimensionType = instance.getDimensionType();
if (dimensionType != instanceDimensionType)
sendDimension(instanceDimensionType);
}
@ -557,7 +560,6 @@ public class Player extends LivingEntity implements CommandSender {
if (chunk != null) {
viewableChunks.add(chunk);
chunk.addViewer(this);
instance.sendChunk(this, chunk);
if (chunk.getChunkX() == Math.floorDiv((int) getPosition().getX(), 16) && chunk.getChunkZ() == Math.floorDiv((int) getPosition().getZ(), 16))
updateViewPosition(chunk);
}
@ -1196,7 +1198,7 @@ public class Player extends LivingEntity implements CommandSender {
* It does remove and add the player from the chunks viewers list when removed or added
* It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent}
*
* @param newChunk the current/new player chunk
* @param newChunk the current/new player chunk
*/
protected void onChunkChange(Chunk newChunk) {
final long[] lastVisibleChunks = new long[viewableChunks.size()];
@ -1236,7 +1238,6 @@ public class Player extends LivingEntity implements CommandSender {
}
this.viewableChunks.add(chunk);
chunk.addViewer(this);
instance.sendChunk(this, chunk);
});
}
}

View File

@ -15,18 +15,21 @@ import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.UpdateConsumer;
import net.minestom.server.network.PacketWriterUtils;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.player.PlayerUtils;
import net.minestom.server.utils.time.CooldownUtils;
import net.minestom.server.utils.time.UpdateOption;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.biomes.Biome;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
// TODO light data & API
public abstract class Chunk implements Viewable {
@ -56,7 +59,7 @@ public abstract class Chunk implements Viewable {
// (block index)/(last update in ms)
protected Int2LongMap updatableBlocksLastUpdate = new Int2LongOpenHashMap();
protected volatile boolean packetUpdated;
protected AtomicBoolean packetUpdated = new AtomicBoolean(false);
// Block entities
protected Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
@ -176,10 +179,22 @@ public abstract class Chunk implements Viewable {
return chunkZ;
}
/**
* Get the cached data packet
* <p>
* Use {@link #retrieveDataBuffer(Consumer)} to be sure to get the updated version
*
* @return the current cached data packet, can be null or outdated
*/
public ByteBuf getFullDataPacket() {
return fullDataPacket;
}
public void setFullDataPacket(ByteBuf fullDataPacket) {
this.fullDataPacket = fullDataPacket;
this.packetUpdated.set(true);
}
protected boolean isBlockEntity(short blockStateId) {
final Block block = Block.fromStateId(blockStateId);
return block.hasBlockEntity();
@ -189,17 +204,6 @@ public abstract class Chunk implements Viewable {
return blockEntities;
}
/**
* Get the columnar space linked to this chunk
* <p>
* Used internally by the pathfinder
*
* @return this chunk columnar space
*/
public PFColumnarSpace getColumnarSpace() {
return columnarSpace;
}
/**
* Change this chunk columnar space
*
@ -209,19 +213,47 @@ public abstract class Chunk implements Viewable {
this.columnarSpace = columnarSpace;
}
public void setFullDataPacket(ByteBuf fullDataPacket) {
this.fullDataPacket = fullDataPacket;
this.packetUpdated = true;
/**
* Retrieve the updated data packet
*
* @param consumer the consumer called once the packet is sure to be up-to-date
*/
public void retrieveDataBuffer(Consumer<ByteBuf> consumer) {
final ByteBuf data = getFullDataPacket();
if (data == null || !packetUpdated.get()) {
PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), packet -> {
setFullDataPacket(packet);
consumer.accept(packet);
});
} else {
consumer.accept(data);
}
}
/**
* Serialize the chunk
*
* @return the serialized chunk
* @throws IOException
*/
protected abstract byte[] getSerializedData() throws IOException;
/**
* Get a {@link ChunkDataPacket} which should contain the full chunk
*
* @return a fresh full chunk data packet
*/
public ChunkDataPacket getFreshFullDataPacket() {
ChunkDataPacket fullDataPacket = getFreshPacket();
fullDataPacket.fullChunk = true;
return fullDataPacket;
}
/**
* Get a {@link ChunkDataPacket} which should contain the non-full chunk
*
* @return a fresh non-full chunk data packet
*/
public ChunkDataPacket getFreshPartialDataPacket() {
ChunkDataPacket fullDataPacket = getFreshPacket();
fullDataPacket.fullChunk = false;
@ -233,20 +265,6 @@ public abstract class Chunk implements Viewable {
*/
protected abstract ChunkDataPacket getFreshPacket();
// Write the packet in the current thread
public void refreshDataPacket() {
final ByteBuf buffer = PacketUtils.writePacket(getFreshFullDataPacket());
setFullDataPacket(buffer);
}
// Write the packet in the writer thread pools
public void refreshDataPacket(Runnable runnable) {
PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), buf -> {
setFullDataPacket(buf);
runnable.run();
});
}
/**
* Used to verify if the chunk should still be kept in memory
*
@ -266,6 +284,9 @@ public abstract class Chunk implements Viewable {
public boolean addViewer(Player player) {
final boolean result = this.viewers.add(player);
// Send the chunk data & light packets to the player
sendChunk(player);
PlayerChunkLoadEvent playerChunkLoadEvent = new PlayerChunkLoadEvent(player, chunkX, chunkZ);
player.callEvent(PlayerChunkLoadEvent.class, playerChunkLoadEvent);
return result;
@ -286,6 +307,101 @@ public abstract class Chunk implements Viewable {
return Collections.unmodifiableSet(viewers);
}
/**
* Send the chunk data to {@code player}
*
* @param player the player
*/
protected void sendChunk(Player player) {
if (!isLoaded())
return;
if (!PlayerUtils.isNettyClient(player))
return;
final PlayerConnection playerConnection = player.getPlayerConnection();
retrieveDataBuffer(buf -> {
playerConnection.sendPacket(buf, true);
});
// TODO do not hardcode
if (MinecraftServer.isFixLighting()) {
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
updateLightPacket.chunkX = getChunkX();
updateLightPacket.chunkZ = getChunkZ();
updateLightPacket.skyLightMask = 0x3FFF0;
updateLightPacket.blockLightMask = 0x3F;
updateLightPacket.emptySkyLightMask = 0x0F;
updateLightPacket.emptyBlockLightMask = 0x3FFC0;
byte[] bytes = new byte[2048];
Arrays.fill(bytes, (byte) 0xFF);
List<byte[]> temp = new ArrayList<>();
List<byte[]> temp2 = new ArrayList<>();
for (int i = 0; i < 14; ++i) {
temp.add(bytes);
}
for (int i = 0; i < 6; ++i) {
temp2.add(bytes);
}
updateLightPacket.skyLight = temp;
updateLightPacket.blockLight = temp2;
PacketWriterUtils.writeAndSend(player, updateLightPacket);
}
}
/**
* Send a full {@link ChunkDataPacket} to {@code player}
*
* @param player the player to update the chunk to
*/
public void sendChunkUpdate(Player player) {
retrieveDataBuffer(buf -> {
final PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(buf, true);
});
}
/**
* Send a full {@link ChunkDataPacket} to all chunk viewers
*/
public void sendChunkUpdate() {
final Set<Player> chunkViewers = getViewers();
if (!chunkViewers.isEmpty()) {
retrieveDataBuffer(buf -> {
chunkViewers.forEach(player -> {
final PlayerConnection playerConnection = player.getPlayerConnection();
if (!PlayerUtils.isNettyClient(playerConnection))
return;
playerConnection.sendPacket(buf, true);
});
});
}
}
/**
* Send a chunk section update packet to {@code player}
*
* @param section the section to update
* @param player the player to send the packet to
*/
public void sendChunkSectionUpdate(int section, Player player) {
if (!PlayerUtils.isNettyClient(player))
return;
PacketWriterUtils.writeAndSend(player, getChunkSectionUpdatePacket(section));
}
protected ChunkDataPacket getChunkSectionUpdatePacket(int section) {
ChunkDataPacket chunkDataPacket = getFreshPartialDataPacket();
chunkDataPacket.fullChunk = false;
int[] sections = new int[16];
sections[section] = 1;
chunkDataPacket.sections = sections;
return chunkDataPacket;
}
/**
* Set the chunk as "unloaded"
*/

View File

@ -19,188 +19,189 @@ import java.util.concurrent.CopyOnWriteArraySet;
public class DynamicChunk extends Chunk {
public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
private short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
private short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
public DynamicChunk(Biome[] biomes, int chunkX, int chunkZ) {
super(biomes, chunkX, chunkZ);
}
public DynamicChunk(Biome[] biomes, int chunkX, int chunkZ) {
super(biomes, chunkX, chunkZ);
}
@Override
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
this.customBlocksId[index] = 0; // Set to none
this.blocksData.remove(index);
@Override
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
this.customBlocksId[index] = 0; // Set to none
this.blocksData.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
}
this.blockEntities.remove(index);
}
@Override
protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
final int index = getBlockIndex(x, y, z);
if (blockStateId != 0
|| (blockStateId == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found
this.blocksStateId[index] = blockStateId;
this.customBlocksId[index] = customId;
} else {
// Block has been deleted, clear cache and return
@Override
protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
final int index = getBlockIndex(x, y, z);
if (blockStateId != 0
|| (blockStateId == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found
this.blocksStateId[index] = blockStateId;
this.customBlocksId[index] = customId;
} else {
// Block has been deleted, clear cache and return
this.blocksStateId[index] = 0; // Set to air
this.blocksStateId[index] = 0; // Set to air
this.blocksData.remove(index);
this.blocksData.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
this.blockEntities.remove(index);
this.packetUpdated = false;
return;
}
this.packetUpdated.set(false);
return;
}
// Set the new data (or remove from the map if is null)
if (data != null) {
this.blocksData.put(index, data);
} else {
this.blocksData.remove(index);
}
// Set the new data (or remove from the map if is null)
if (data != null) {
this.blocksData.put(index, data);
} else {
this.blocksData.remove(index);
}
// Set update consumer
if (updateConsumer != null) {
this.updatableBlocks.add(index);
this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis());
} else {
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
}
// Set update consumer
if (updateConsumer != null) {
this.updatableBlocks.add(index);
this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis());
} else {
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
}
if (isBlockEntity(blockStateId)) {
this.blockEntities.add(index);
} else {
this.blockEntities.remove(index);
}
// Set block entity
if (isBlockEntity(blockStateId)) {
this.blockEntities.add(index);
} else {
this.blockEntities.remove(index);
}
this.packetUpdated = false;
this.packetUpdated.set(false);
if (columnarSpace != null) {
final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields();
final PFBlockDescription blockDescription = new PFBlockDescription(Block.fromStateId(blockStateId));
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
}
}
if (columnarSpace != null) {
final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields();
final PFBlockDescription blockDescription = new PFBlockDescription(Block.fromStateId(blockStateId));
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
}
}
@Override
public short getBlockStateId(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return 0; // TODO: custom invalid block
}
final short id = blocksStateId[index];
return id;
}
@Override
public short getBlockStateId(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return 0; // TODO: custom invalid block
}
final short id = blocksStateId[index];
return id;
}
@Override
public short getCustomBlockId(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return 0; // TODO: custom invalid block
}
final short id = customBlocksId[index];
return id;
}
@Override
public short getCustomBlockId(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return 0; // TODO: custom invalid block
}
final short id = customBlocksId[index];
return id;
}
@Override
public CustomBlock getCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return null; // TODO: custom invalid block
}
final short id = customBlocksId[index];
return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null;
}
@Override
public CustomBlock getCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return null; // TODO: custom invalid block
}
final short id = customBlocksId[index];
return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null;
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
this.blocksStateId[blockIndex] = blockStateId;
this.customBlocksId[blockIndex] = customId;
}
this.blocksStateId[blockIndex] = blockStateId;
this.customBlocksId[blockIndex] = customId;
}
@Override
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
@Override
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
this.blocksStateId[blockIndex] = blockStateId;
}
this.blocksStateId[blockIndex] = blockStateId;
}
@Override
protected byte[] getSerializedData() throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(output);
@Override
protected byte[] getSerializedData() throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(output);
for (int i = 0; i < BIOME_COUNT; i++) {
dos.writeByte(biomes[i].getId());
}
for (int i = 0; i < BIOME_COUNT; i++) {
dos.writeByte(biomes[i].getId());
}
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
for (short y = 0; y < CHUNK_SIZE_Y; y++) {
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
final int index = getBlockIndex(x, y, z);
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
for (short y = 0; y < CHUNK_SIZE_Y; y++) {
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
final int index = getBlockIndex(x, y, z);
final short blockStateId = blocksStateId[index];
final short customBlockId = customBlocksId[index];
final short blockStateId = blocksStateId[index];
final short customBlockId = customBlocksId[index];
if (blockStateId == 0 && customBlockId == 0)
continue;
if (blockStateId == 0 && customBlockId == 0)
continue;
final Data data = blocksData.get(index);
final Data data = blocksData.get(index);
// Chunk coordinates
dos.writeInt(x);
dos.writeInt(y);
dos.writeInt(z);
// Chunk coordinates
dos.writeInt(x);
dos.writeInt(y);
dos.writeInt(z);
// Id
dos.writeShort(blockStateId);
dos.writeShort(customBlockId);
// Id
dos.writeShort(blockStateId);
dos.writeShort(customBlockId);
// Data
final boolean hasData = (data != null && (data instanceof SerializableData));
dos.writeBoolean(hasData);
if (hasData) {
final byte[] d = ((SerializableData) data).getSerializedData();
dos.writeInt(d.length);
dos.write(d);
}
}
}
}
// Data
final boolean hasData = (data != null && (data instanceof SerializableData));
dos.writeBoolean(hasData);
if (hasData) {
final byte[] d = ((SerializableData) data).getSerializedData();
dos.writeInt(d.length);
dos.write(d);
}
}
}
}
final byte[] result = output.toByteArray();
return result;
}
final byte[] result = output.toByteArray();
return result;
}
@Override
protected ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
fullDataPacket.blocksStateId = blocksStateId.clone();
fullDataPacket.customBlocksId = customBlocksId.clone();
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
@Override
protected ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
fullDataPacket.blocksStateId = blocksStateId.clone();
fullDataPacket.customBlocksId = customBlocksId.clone();
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
}

View File

@ -1,6 +1,5 @@
package net.minestom.server.instance;
import io.netty.buffer.ByteBuf;
import net.minestom.server.MinecraftServer;
import net.minestom.server.UpdateManager;
import net.minestom.server.data.Data;
@ -19,13 +18,11 @@ import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.network.PacketWriterUtils;
import net.minestom.server.network.packet.server.play.BlockActionPacket;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
import net.minestom.server.storage.StorageFolder;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.player.PlayerUtils;
import net.minestom.server.utils.time.CooldownUtils;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.time.UpdateOption;
@ -227,21 +224,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
protected abstract void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
/**
* Send all chunks data to {@code player}
*
* @param player the player
*/
public abstract void sendChunks(Player player);
/**
* Send a specific chunk data to {@code player}
*
* @param player the player
* @param chunk the chunk
*/
public abstract void sendChunk(Player player, Chunk chunk);
/**
* When set to true, chunks will load with players moving closer
* Otherwise using {@link #loadChunk(int, int)} will be required to even spawn a player
@ -264,50 +246,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
*/
public abstract boolean isInVoid(Position position);
//
/**
* Send a full {@link ChunkDataPacket} to {@code player}
*
* @param player the player to update the chunk to
* @param chunk the chunk to send
*/
public void sendChunkUpdate(Player player, Chunk chunk) {
player.getPlayerConnection().sendPacket(chunk.getFullDataPacket(), true);
}
protected void sendChunkUpdate(Collection<Player> players, Chunk chunk) {
final ByteBuf chunkData = chunk.getFullDataPacket();
players.forEach(player -> {
if (!PlayerUtils.isNettyClient(player))
return;
player.getPlayerConnection().sendPacket(chunkData, true);
});
}
protected void sendChunkSectionUpdate(Chunk chunk, int section, Collection<Player> players) {
PacketWriterUtils.writeAndSend(players, getChunkSectionUpdatePacket(chunk, section));
}
public void sendChunkSectionUpdate(Chunk chunk, int section, Player player) {
if (!PlayerUtils.isNettyClient(player))
return;
PacketWriterUtils.writeAndSend(player, getChunkSectionUpdatePacket(chunk, section));
}
protected ChunkDataPacket getChunkSectionUpdatePacket(Chunk chunk, int section) {
ChunkDataPacket chunkDataPacket = chunk.getFreshPartialDataPacket();
chunkDataPacket.fullChunk = false;
int[] sections = new int[16];
sections[section] = 1;
chunkDataPacket.sections = sections;
return chunkDataPacket;
}
//
/**
* Get if the instance has been registered in {@link InstanceManager}
*
@ -783,7 +721,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
if (isPlayer) {
final Player player = (Player) entity;
sendChunks(player);
getWorldBorder().init(player);
}

View File

@ -1,6 +1,5 @@
package net.minestom.server.instance;
import io.netty.buffer.ByteBuf;
import lombok.Setter;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.Data;
@ -12,20 +11,18 @@ import net.minestom.server.event.player.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.BlockProvider;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
import net.minestom.server.network.PacketWriterUtils;
import net.minestom.server.network.packet.server.play.BlockChangePacket;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.ParticleCreator;
import net.minestom.server.storage.StorageFolder;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.player.PlayerUtils;
import net.minestom.server.utils.thread.MinestomThread;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.validate.Check;
@ -41,7 +38,6 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* InstanceContainer is an instance that contains chunks in contrary to SharedInstance.
@ -66,7 +62,7 @@ public class InstanceContainer extends Instance {
private boolean autoChunkLoad;
@Setter
private BiFunction<Integer, Integer, Function<BlockPosition, Short>> chunkDecider;
private BiFunction<Integer, Integer, BlockProvider> chunkDecider;
public InstanceContainer(UUID uniqueId, DimensionType dimensionType, StorageFolder storageFolder) {
super(uniqueId, dimensionType);
@ -256,7 +252,7 @@ public class InstanceContainer extends Instance {
// The player probably have a wrong version of this chunk section, send it
if (blockStateId == 0) {
sendChunkSectionUpdate(chunk, ChunkUtils.getSectionAt(y), player);
chunk.sendChunkSectionUpdate(ChunkUtils.getSectionAt(y), player);
return false;
}
@ -286,7 +282,7 @@ public class InstanceContainer extends Instance {
} else {
// Cancelled so we need to refresh player chunk section
final int section = ChunkUtils.getSectionAt(blockPosition.getY());
sendChunkSectionUpdate(chunk, section, player);
chunk.sendChunkSectionUpdate(section, player);
}
return result;
}
@ -439,11 +435,19 @@ public class InstanceContainer extends Instance {
chunkGenerator.fillBiomes(biomes, chunkX, chunkZ);
}
Function<BlockPosition, Short> chunkSuppler = chunkDecider != null ? chunkDecider.apply(chunkX, chunkZ) : null;
final Chunk chunk = chunkSuppler == null ? new DynamicChunk(biomes, chunkX, chunkZ) : new StaticChunk(biomes, chunkX, chunkZ, chunkSuppler) ;
final Chunk chunk;
final BlockProvider blockProvider = chunkDecider != null ? chunkDecider.apply(chunkX, chunkZ) : null;
if (blockProvider != null) {
// Use static chunk
chunk = new StaticChunk(biomes, chunkX, chunkZ, blockProvider);
} else {
// Use dynamic chunk
chunk = new DynamicChunk(biomes, chunkX, chunkZ);
}
cacheChunk(chunk);
if (chunkGenerator != null && chunkSuppler == null) {
if (chunkGenerator != null && blockProvider == null) {
final ChunkBatch chunkBatch = createChunkBatch(chunk);
chunkBatch.flushChunkGenerator(chunkGenerator, callback);
@ -457,62 +461,6 @@ public class InstanceContainer extends Instance {
callChunkLoadEvent(chunkX, chunkZ);
}
public void sendChunkUpdate(Chunk chunk) {
final Set<Player> chunkViewers = chunk.getViewers();
if (!chunkViewers.isEmpty()) {
sendChunkUpdate(chunkViewers, chunk);
}
}
@Override
public void sendChunks(Player player) {
for (Chunk chunk : getChunks()) {
sendChunk(player, chunk);
}
}
@Override
public void sendChunk(Player player, Chunk chunk) {
if (!chunk.isLoaded())
return;
if (!PlayerUtils.isNettyClient(player))
return;
final ByteBuf data = chunk.getFullDataPacket();
if (data == null || !chunk.packetUpdated) {
PacketWriterUtils.writeCallbackPacket(chunk.getFreshFullDataPacket(), packet -> {
chunk.setFullDataPacket(packet);
sendChunkUpdate(player, chunk);
});
} else {
sendChunkUpdate(player, chunk);
}
// TODO do not hardcode
if (MinecraftServer.isFixLighting()) {
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
updateLightPacket.chunkX = chunk.getChunkX();
updateLightPacket.chunkZ = chunk.getChunkZ();
updateLightPacket.skyLightMask = 0x3FFF0;
updateLightPacket.blockLightMask = 0x3F;
updateLightPacket.emptySkyLightMask = 0x0F;
updateLightPacket.emptyBlockLightMask = 0x3FFC0;
byte[] bytes = new byte[2048];
Arrays.fill(bytes, (byte) 0xFF);
List<byte[]> temp = new ArrayList<>();
List<byte[]> temp2 = new ArrayList<>();
for (int i = 0; i < 14; ++i) {
temp.add(bytes);
}
for (int i = 0; i < 6; ++i) {
temp2.add(bytes);
}
updateLightPacket.skyLight = temp;
updateLightPacket.blockLight = temp2;
PacketWriterUtils.writeAndSend(player, updateLightPacket);
}
}
@Override
public void enableAutoChunkLoad(boolean enable) {
this.autoChunkLoad = enable;

View File

@ -101,11 +101,6 @@ public class SharedInstance extends Instance {
instanceContainer.setStorageFolder(storageFolder);
}
@Override
public void sendChunkSectionUpdate(Chunk chunk, int section, Player player) {
instanceContainer.sendChunkSectionUpdate(chunk, section, player);
}
@Override
public void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
instanceContainer.retrieveChunk(chunkX, chunkZ, callback);
@ -116,16 +111,6 @@ public class SharedInstance extends Instance {
instanceContainer.createChunk(chunkX, chunkZ, callback);
}
@Override
public void sendChunks(Player player) {
instanceContainer.sendChunks(player);
}
@Override
public void sendChunk(Player player, Chunk chunk) {
instanceContainer.sendChunk(player, chunk);
}
@Override
public void enableAutoChunkLoad(boolean enable) {
instanceContainer.enableAutoChunkLoad(enable);

View File

@ -2,82 +2,82 @@ package net.minestom.server.instance;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.data.Data;
import net.minestom.server.instance.block.BlockProvider;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.UpdateConsumer;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Function;
public class StaticChunk extends Chunk {
final Function<BlockPosition, Short> blockProvider;
protected final BlockProvider blockProvider;
public StaticChunk(Biome[] biomes, int chunkX, int chunkZ, Function<BlockPosition, Short> blockProvider) {
super(biomes, chunkX, chunkZ);
this.blockProvider = blockProvider;
}
public StaticChunk(Biome[] biomes, int chunkX, int chunkZ, BlockProvider blockProvider) {
super(biomes, chunkX, chunkZ);
this.blockProvider = blockProvider;
}
@Override
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
//noop
}
@Override
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
//noop
}
@Override
protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
//noop
}
@Override
protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
//noop
}
@Override
public short getBlockStateId(int x, int y, int z) {
return blockProvider.apply(new BlockPosition(x, y, z));
}
@Override
public short getBlockStateId(int x, int y, int z) {
return blockProvider.getBlockStateId(x, y, z);
}
@Override
public short getCustomBlockId(int x, int y, int z) {
//noop
return 0;
}
@Override
public short getCustomBlockId(int x, int y, int z) {
//noop
return 0;
}
@Override
public CustomBlock getCustomBlock(int x, int y, int z) {
//noop
return null;
}
@Override
public CustomBlock getCustomBlock(int x, int y, int z) {
//noop
return null;
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
//noop
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
//noop
}
@Override
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
//noop
}
@Override
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
//noop
}
@Override
protected byte[] getSerializedData(){
return null;
}
@Override
protected byte[] getSerializedData() {
return null;
}
@Override
protected ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
for (int i = 0; i < blocksStateId.length; i++) {
blocksStateId[i] = blockProvider.apply(ChunkUtils.getBlockPosition(i, 0, 0));
}
fullDataPacket.blocksStateId = blocksStateId;
fullDataPacket.customBlocksId = new short[0];
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
@Override
protected ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
for (int i = 0; i < blocksStateId.length; i++) {
final int[] pos = ChunkUtils.indexToPosition(i, 0, 0);
blocksStateId[i] = blockProvider.getBlockStateId(pos[0], pos[1], pos[2]);
}
fullDataPacket.blocksStateId = blocksStateId;
fullDataPacket.customBlocksId = new short[0];
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
}

View File

@ -74,9 +74,8 @@ public class BlockBatch implements InstanceBatch {
data.apply(chunk);
}
chunk.refreshDataPacket(() -> {
instance.sendChunkUpdate(chunk);
});
// Refresh chunk for viewers
chunk.sendChunkUpdate();
if (isLast) {
if (callback != null)

View File

@ -102,9 +102,8 @@ public class ChunkBatch implements InstanceBatch {
data.apply(chunk);
}
chunk.refreshDataPacket(() -> {
instance.sendChunkUpdate(chunk);
});
// Refresh chunk for viewers
chunk.sendChunkUpdate();
if (callback != null)
callback.accept(chunk);

View File

@ -0,0 +1,6 @@
package net.minestom.server.instance.block;
@FunctionalInterface
public interface BlockProvider {
short getBlockStateId(int x, int y, int z);
}

View File

@ -136,7 +136,7 @@ public class BlockPlacementListener {
// Refresh chunk section if needed
if (refreshChunk) {
instance.sendChunkSectionUpdate(chunk, ChunkUtils.getSectionAt(blockPosition.getY()), player);
chunk.sendChunkSectionUpdate(ChunkUtils.getSectionAt(blockPosition.getY()), player);
}
player.getInventory().refreshSlot(player.getHeldSlot());