Fixes & added data serializer

This commit is contained in:
TheMode 2019-09-07 11:42:33 +02:00
parent b517c1091e
commit 879f9e7c42
25 changed files with 391 additions and 120 deletions

View File

@ -18,5 +18,6 @@ dependencies {
implementation 'com.github.luben:zstd-jni:1.4.3-1' implementation 'com.github.luben:zstd-jni:1.4.3-1'
implementation 'com.esotericsoftware:reflectasm:1.11.9' implementation 'com.esotericsoftware:reflectasm:1.11.9'
implementation 'com.github.LynnOwens:starlite:9971b899f7' implementation 'com.github.LynnOwens:starlite:9971b899f7'
implementation 'com.github.jhg023:SimpleNet:1.5.0' //implementation 'com.github.jhg023:SimpleNet:1.5.0'
implementation 'com.github.jhg023:SimpleNet:97dbc4951e'
} }

View File

@ -21,8 +21,9 @@ public class Main {
// Thread number // Thread number
public static final int THREAD_COUNT_PACKET_WRITER = 2; public static final int THREAD_COUNT_PACKET_WRITER = 2;
public static final int THREAD_COUNT_CHUNK_IO = 2; public static final int THREAD_COUNT_IO = 2;
public static final int THREAD_COUNT_CHUNK_BATCH = 2; public static final int THREAD_COUNT_CHUNK_BATCH = 2;
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
public static final int THREAD_COUNT_ENTITIES = 2; public static final int THREAD_COUNT_ENTITIES = 2;
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2; public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;

View File

@ -1,13 +1,24 @@
package fr.themode.minestom.data; package fr.themode.minestom.data;
import fr.themode.minestom.Main;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class Data { public class Data {
private ConcurrentHashMap<String, Object> data = new ConcurrentHashMap(); private DataManager dataManager = Main.getDataManager();
private ConcurrentHashMap<String, DataType> dataType = new ConcurrentHashMap<>();
public <T> void set(String key, T value, DataType<T> type) { private ConcurrentHashMap<String, Object> data = new ConcurrentHashMap();
private ConcurrentHashMap<String, Class> dataType = new ConcurrentHashMap<>();
public <T> void set(String key, T value, Class<T> type) {
if (dataManager.getDataType(type) == null) {
throw new UnsupportedOperationException("Type " + type.getName() + " hasn't been registered in DataManager#registerType");
}
this.data.put(key, value); this.data.put(key, value);
this.dataType.put(key, type); this.dataType.put(key, type);
} }
@ -20,6 +31,32 @@ public class Data {
return (T) data.getOrDefault(key, defaultValue); return (T) data.getOrDefault(key, defaultValue);
} }
// TODO serialization public byte[] getSerializedData() throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(output);
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = entry.getKey();
Class type = dataType.get(key);
Object value = entry.getValue();
DataType dataType = Main.getDataManager().getDataType(type);
byte[] encodedType = type.getName().getBytes(); // Data type
dos.writeShort(encodedType.length);
dos.write(encodedType);
byte[] encodedName = key.getBytes(); // Data name
dos.writeShort(encodedName.length);
dos.write(encodedName);
byte[] encodedValue = dataType.encode(value); // Data
dos.writeInt(encodedValue.length);
dos.write(encodedValue);
}
dos.writeShort(0xff); // End of data object
return output.toByteArray();
}
} }

View File

@ -1,9 +1,113 @@
package fr.themode.minestom.data; package fr.themode.minestom.data;
import fr.themode.minestom.Main;
import fr.themode.minestom.io.IOManager;
import fr.themode.minestom.utils.CompressionUtils;
import java.io.*;
import java.nio.file.Files;
import java.util.function.Consumer;
public interface DataContainer { public interface DataContainer {
Data getData(); Data getData();
void setData(Data data); void setData(Data data);
default void saveData(File file, Runnable callback) {
IOManager.submit(() -> {
Data data = getData();
if (data == null) {
// TODO error trying to save null data
return;
}
try (FileOutputStream fos = new FileOutputStream(file)) {
byte[] serializedData = data.getSerializedData();
fos.write(CompressionUtils.getCompressedData(serializedData));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (callback != null)
callback.run();
});
}
default void saveData(File file) {
saveData(file, null);
}
default void loadData(File file, Consumer<Data> callback) {
IOManager.submit(() -> {
if (!file.exists()) {
setData(new Data());
if (callback != null)
callback.accept(getData());
System.out.println("FILE DATA NOT FOUND, NEW DATA OBJECT CREATED");
return;
}
byte[] array;
try {
array = Files.readAllBytes(file.toPath());
} catch (IOException e) {
e.printStackTrace(); // Unknown error
return;
}
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(CompressionUtils.getDecompressedData(array)));
Data data = new Data();
try {
while (true) {
short typeLength = stream.readShort();
if (typeLength == 0xff) {
// End of data
break;
}
byte[] typeCache = new byte[typeLength];
for (int i = 0; i < typeLength; i++) {
typeCache[i] = stream.readByte();
}
short nameLength = stream.readShort();
byte[] nameCache = new byte[nameLength];
for (int i = 0; i < nameLength; i++) {
nameCache[i] = stream.readByte();
}
int valueLength = stream.readInt();
byte[] valueCache = new byte[valueLength];
for (int i = 0; i < valueLength; i++) {
valueCache[i] = stream.readByte();
}
Class type = Class.forName(new String(typeCache));
String name = new String(nameCache);
Object value = Main.getDataManager().getDataType(type).decode(valueCache);
data.set(name, value, type);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
setData(data);
if (callback != null)
callback.accept(data);
});
}
default void loadData(File file) {
loadData(file, null);
}
} }

View File

@ -0,0 +1,29 @@
package fr.themode.minestom.data;
import fr.themode.minestom.data.type.IntegerData;
import fr.themode.minestom.utils.PrimitiveConversion;
import java.util.HashMap;
import java.util.Map;
public class DataManager {
private Map<Class, DataType> dataTypeMap = new HashMap<>();
{
registerType(Integer.class, new IntegerData());
}
public <T> void registerType(Class<T> clazz, DataType<T> dataType) {
clazz = PrimitiveConversion.getObjectClass(clazz);
if (dataTypeMap.containsKey(clazz))
throw new UnsupportedOperationException("Type " + clazz.getName() + " has already been registed");
this.dataTypeMap.put(clazz, dataType);
}
public <T> DataType<T> getDataType(Class<T> clazz) {
return dataTypeMap.get(PrimitiveConversion.getObjectClass(clazz));
}
}

View File

@ -5,18 +5,18 @@ import fr.themode.minestom.event.PlayerLoginEvent;
import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.InstanceManager; import fr.themode.minestom.instance.InstanceManager;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class EntityManager { public class EntityManager {
private static InstanceManager instanceManager = Main.getInstanceManager(); private static InstanceManager instanceManager = Main.getInstanceManager();
private ExecutorService entitiesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_ENTITIES); private ExecutorService entitiesPool = new MinestomThread(Main.THREAD_COUNT_ENTITIES, "Ms-EntitiesPool");
private ExecutorService playersPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PLAYERS_ENTITIES); private ExecutorService playersPool = new MinestomThread(Main.THREAD_COUNT_PLAYERS_ENTITIES, "Ms-PlayersPool");
private ConcurrentLinkedQueue<Player> waitingPlayers = new ConcurrentLinkedQueue<>(); private ConcurrentLinkedQueue<Player> waitingPlayers = new ConcurrentLinkedQueue<>();
@ -25,7 +25,6 @@ public class EntityManager {
for (Instance instance : instanceManager.getInstances()) { for (Instance instance : instanceManager.getInstances()) {
testTick2(instance); testTick2(instance);
} }
} }
private void waitingPlayersTick() { private void waitingPlayersTick() {
@ -38,7 +37,6 @@ public class EntityManager {
Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstanceContainer() : loginEvent.getSpawningInstance(); Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstanceContainer() : loginEvent.getSpawningInstance();
playerCache.setInstance(spawningInstance); playerCache.setInstance(spawningInstance);
}); });
} }
} }

View File

@ -4,7 +4,6 @@ import fr.themode.minestom.net.packet.server.play.SpawnObjectPacket;
import fr.themode.minestom.net.player.PlayerConnection; import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.utils.EntityUtils; import fr.themode.minestom.utils.EntityUtils;
// TODO viewers synchronization each X ticks?
public abstract class ObjectEntity extends Entity { public abstract class ObjectEntity extends Entity {
public ObjectEntity(int entityType) { public ObjectEntity(int entityType) {

View File

@ -4,7 +4,6 @@ import fr.themode.minestom.Main;
import fr.themode.minestom.bossbar.BossBar; import fr.themode.minestom.bossbar.BossBar;
import fr.themode.minestom.chat.Chat; import fr.themode.minestom.chat.Chat;
import fr.themode.minestom.collision.BoundingBox; import fr.themode.minestom.collision.BoundingBox;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.property.Attribute; import fr.themode.minestom.entity.property.Attribute;
import fr.themode.minestom.event.*; import fr.themode.minestom.event.*;
import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Chunk;
@ -23,6 +22,7 @@ import fr.themode.minestom.utils.*;
import fr.themode.minestom.world.Dimension; import fr.themode.minestom.world.Dimension;
import fr.themode.minestom.world.LevelType; import fr.themode.minestom.world.LevelType;
import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
@ -124,12 +124,21 @@ public class Player extends LivingEntity {
} }
}); });
setEventCallback(PlayerBlockPlaceEvent.class, event -> { final String dataFileName = "C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data\\" + getUsername() + ".dat";
/*sendMessage("Placed block! " + event.getHand());
int value = getData().getOrDefault("test", 0); setEventCallback(PlayerDisconnectEvent.class, event -> {
getData().set("test", value + 1, DataType.INTEGER); saveData(new File(dataFileName), () -> {
System.out.println("SAVE DONE");
});
});
setEventCallback(PlayerBlockPlaceEvent.class, event -> {
if (getData() != null) {
int value = getData().getOrDefault("test", 0);
getData().set("test", value + 1, Integer.class);
System.out.println("OLD DATA VALUE: " + value);
}
System.out.println("OLD DATA VALUE: " + value);*/
if (event.getHand() != Hand.MAIN) if (event.getHand() != Hand.MAIN)
return; return;
@ -151,18 +160,20 @@ public class Player extends LivingEntity {
setEventCallback(PlayerLoginEvent.class, event -> { setEventCallback(PlayerLoginEvent.class, event -> {
event.setSpawningInstance(instanceContainer); event.setSpawningInstance(instanceContainer);
setData(new Data()); loadData(new File(dataFileName));
}); });
setEventCallback(PlayerSpawnEvent.class, event -> { setEventCallback(PlayerSpawnEvent.class, event -> {
System.out.println("SPAWN"); setGameMode(GameMode.CREATIVE);
setGameMode(GameMode.SURVIVAL);
teleport(new Position(0, 66, 0)); teleport(new Position(0, 66, 0));
/*ChickenCreature chickenCreature = new ChickenCreature(); /*Random random = new Random();
chickenCreature.refreshPosition(2, 65, 2); for (int i = 0; i < 50; i++) {
chickenCreature.setInstance(getInstance()); ChickenCreature chickenCreature = new ChickenCreature();
chickenCreature.addPassenger(this);*/ chickenCreature.refreshPosition(random.nextInt(100), 65, random.nextInt(100));
chickenCreature.setInstance(getInstance());
}*/
//chickenCreature.addPassenger(this);
/*for (int ix = 0; ix < 4; ix++) /*for (int ix = 0; ix < 4; ix++)
for (int iz = 0; iz < 4; iz++) { for (int iz = 0; iz < 4; iz++) {
@ -173,7 +184,7 @@ public class Player extends LivingEntity {
//itemEntity.remove(); //itemEntity.remove();
}*/ }*/
getInventory().addItemStack(new ItemStack(541, (byte) 1)); getInventory().addItemStack(new ItemStack(1, (byte) 75));
//getInventory().addItemStack(new ItemStack(1, (byte) 100)); //getInventory().addItemStack(new ItemStack(1, (byte) 100));
TeamsPacket teamsPacket = new TeamsPacket(); TeamsPacket teamsPacket = new TeamsPacket();
@ -198,8 +209,10 @@ public class Player extends LivingEntity {
@Override @Override
public void update() { public void update() {
// Flush all pending packets
playerConnection.flush(); playerConnection.flush();
// Process sent packets
ClientPlayPacket packet; ClientPlayPacket packet;
while ((packet = packets.poll()) != null) { while ((packet = packets.poll()) != null) {
packet.process(this); packet.process(this);
@ -250,64 +263,67 @@ public class Player extends LivingEntity {
// Multiplayer sync // Multiplayer sync
Position position = getPosition(); if (!getViewers().isEmpty()) {
boolean positionChanged = position.getX() != lastX || position.getZ() != lastZ || position.getY() != lastY; Position position = getPosition();
boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch; boolean positionChanged = position.getX() != lastX || position.getZ() != lastZ || position.getY() != lastY;
ServerPacket updatePacket = null; boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch;
ServerPacket optionalUpdatePacket = null; ServerPacket updatePacket = null;
if (positionChanged && viewChanged) { ServerPacket optionalUpdatePacket = null;
EntityLookAndRelativeMovePacket entityLookAndRelativeMovePacket = new EntityLookAndRelativeMovePacket(); if (positionChanged && viewChanged) {
entityLookAndRelativeMovePacket.entityId = getEntityId(); EntityLookAndRelativeMovePacket entityLookAndRelativeMovePacket = new EntityLookAndRelativeMovePacket();
entityLookAndRelativeMovePacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128); entityLookAndRelativeMovePacket.entityId = getEntityId();
entityLookAndRelativeMovePacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128); entityLookAndRelativeMovePacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
entityLookAndRelativeMovePacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128); entityLookAndRelativeMovePacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
entityLookAndRelativeMovePacket.yaw = position.getYaw(); entityLookAndRelativeMovePacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
entityLookAndRelativeMovePacket.pitch = position.getPitch(); entityLookAndRelativeMovePacket.yaw = position.getYaw();
entityLookAndRelativeMovePacket.onGround = onGround; entityLookAndRelativeMovePacket.pitch = position.getPitch();
entityLookAndRelativeMovePacket.onGround = onGround;
lastX = position.getX(); lastX = position.getX();
lastY = position.getY(); lastY = position.getY();
lastZ = position.getZ(); lastZ = position.getZ();
lastYaw = position.getYaw(); lastYaw = position.getYaw();
lastPitch = position.getPitch(); lastPitch = position.getPitch();
updatePacket = entityLookAndRelativeMovePacket; updatePacket = entityLookAndRelativeMovePacket;
} else if (positionChanged) { } else if (positionChanged) {
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket(); EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
entityRelativeMovePacket.entityId = getEntityId(); entityRelativeMovePacket.entityId = getEntityId();
entityRelativeMovePacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128); entityRelativeMovePacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
entityRelativeMovePacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128); entityRelativeMovePacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
entityRelativeMovePacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128); entityRelativeMovePacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
entityRelativeMovePacket.onGround = onGround; entityRelativeMovePacket.onGround = onGround;
lastX = position.getX(); lastX = position.getX();
lastY = position.getY(); lastY = position.getY();
lastZ = position.getZ(); lastZ = position.getZ();
updatePacket = entityRelativeMovePacket; updatePacket = entityRelativeMovePacket;
} else if (viewChanged) { } else if (viewChanged) {
EntityLookPacket entityLookPacket = new EntityLookPacket(); EntityLookPacket entityLookPacket = new EntityLookPacket();
entityLookPacket.entityId = getEntityId(); entityLookPacket.entityId = getEntityId();
entityLookPacket.yaw = position.getYaw(); entityLookPacket.yaw = position.getYaw();
entityLookPacket.pitch = position.getPitch(); entityLookPacket.pitch = position.getPitch();
entityLookPacket.onGround = onGround; entityLookPacket.onGround = onGround;
lastYaw = position.getYaw(); lastYaw = position.getYaw();
lastPitch = position.getPitch(); lastPitch = position.getPitch();
updatePacket = entityLookPacket; updatePacket = entityLookPacket;
} }
if (viewChanged) { if (viewChanged) {
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket(); EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId(); entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw(); entityHeadLookPacket.yaw = position.getYaw();
optionalUpdatePacket = entityHeadLookPacket; optionalUpdatePacket = entityHeadLookPacket;
} }
if (updatePacket != null) { if (updatePacket != null) {
if (optionalUpdatePacket != null) { if (optionalUpdatePacket != null) {
sendPacketsToViewers(updatePacket, optionalUpdatePacket); sendPacketsToViewers(updatePacket, optionalUpdatePacket);
} else { } else {
sendPacketToViewers(updatePacket); sendPacketToViewers(updatePacket);
}
} }
} }
} }
@Override @Override
@ -327,6 +343,8 @@ public class Player extends LivingEntity {
getOpenInventory().removeViewer(this); getOpenInventory().removeViewer(this);
this.viewableEntities.forEach(entity -> entity.removeViewer(this)); this.viewableEntities.forEach(entity -> entity.removeViewer(this));
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this)); this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
resetTargetBlock();
callEvent(PlayerDisconnectEvent.class, new PlayerDisconnectEvent());
super.remove(); super.remove();
} }
@ -392,6 +410,7 @@ public class Player extends LivingEntity {
if (chunk != null) { if (chunk != null) {
viewableChunks.add(chunk); viewableChunks.add(chunk);
chunk.addViewer(this); chunk.addViewer(this);
chunk.packetUpdated = true;
} }
boolean isLast = counter.get() == length - 1; boolean isLast = counter.get() == length - 1;
if (isLast) { if (isLast) {
@ -807,6 +826,8 @@ public class Player extends LivingEntity {
} }
public void resetTargetBlock() { public void resetTargetBlock() {
if (targetBlockPosition != null)
sendBlockBreakAnimation(targetBlockPosition, (byte) -1); // Clear the break animation
this.targetCustomBlock = null; this.targetCustomBlock = null;
this.targetBlockPosition = null; this.targetBlockPosition = null;
this.targetBlockTime = 0; this.targetBlockTime = 0;

View File

@ -11,6 +11,8 @@ import net.tofweb.starlite.CostBlockManager;
import net.tofweb.starlite.Path; import net.tofweb.starlite.Path;
import net.tofweb.starlite.Pathfinder; import net.tofweb.starlite.Pathfinder;
import java.util.Random;
public class ChickenCreature extends EntityCreature { public class ChickenCreature extends EntityCreature {
private Path path; private Path path;
@ -19,6 +21,9 @@ public class ChickenCreature extends EntityCreature {
private long lastTeleport; private long lastTeleport;
private long wait = 500; private long wait = 500;
private float randomX = new Random().nextFloat();
private float randomZ = new Random().nextFloat();
public ChickenCreature() { public ChickenCreature() {
super(EntityType.CHICKEN); super(EntityType.CHICKEN);
setBoundingBox(0.4f, 0.7f, 0.4f); setBoundingBox(0.4f, 0.7f, 0.4f);
@ -27,7 +32,7 @@ public class ChickenCreature extends EntityCreature {
@Override @Override
public void update() { public void update() {
super.update(); super.update();
float speed = 0.25f; float speed = 0.025f;
if (hasPassenger()) { if (hasPassenger()) {
Entity passenger = getPassengers().iterator().next(); Entity passenger = getPassengers().iterator().next();
@ -81,6 +86,8 @@ public class ChickenCreature extends EntityCreature {
move(x, 0, z, updateView); move(x, 0, z, updateView);
} }
} else {
move(randomX * speed, 0, randomZ * speed, true);
} }
/*if (path == null) { /*if (path == null) {

View File

@ -0,0 +1,4 @@
package fr.themode.minestom.event;
public class PlayerDisconnectEvent extends Event {
}

View File

@ -1,15 +1,17 @@
package fr.themode.minestom.instance; package fr.themode.minestom.instance;
import fr.themode.minestom.Main;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BlockBatch implements BlockModifier { public class BlockBatch implements BlockModifier {
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(2); private static final ExecutorService batchesPool = new MinestomThread(Main.THREAD_COUNT_BLOCK_BATCH, "Ms-BlockBatchPool");
private InstanceContainer instance; private InstanceContainer instance;

View File

@ -33,7 +33,8 @@ public class Chunk implements Viewable {
// Cache // Cache
private Set<Player> viewers = new CopyOnWriteArraySet<>(); private Set<Player> viewers = new CopyOnWriteArraySet<>();
private Packet fullDataPacket; private Packet fullDataPacket;
protected volatile boolean packetUpdated;
public volatile boolean packetUpdated;
public Chunk(Biome biome, int chunkX, int chunkZ) { public Chunk(Biome biome, int chunkX, int chunkZ) {
this.biome = biome; this.biome = biome;
@ -66,9 +67,9 @@ public class Chunk implements Viewable {
this.blocksId[index] = blockType; this.blocksId[index] = blockType;
this.customBlocks[index] = customId; this.customBlocks[index] = customId;
if (isBlockEntity(blockType)) { if (isBlockEntity(blockType)) {
blockEntities.add(index); this.blockEntities.add(index);
} else { } else {
blockEntities.remove(index); this.blockEntities.remove(index);
} }
this.packetUpdated = false; this.packetUpdated = false;
} }

View File

@ -1,11 +1,11 @@
package fr.themode.minestom.instance; package fr.themode.minestom.instance;
import fr.themode.minestom.Main; import fr.themode.minestom.Main;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@ -13,7 +13,7 @@ import java.util.function.Consumer;
*/ */
public class ChunkBatch implements BlockModifier { public class ChunkBatch implements BlockModifier {
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_BATCH); private static final ExecutorService batchesPool = new MinestomThread(Main.THREAD_COUNT_CHUNK_BATCH, "Ms-ChunkBatchPool");
private InstanceContainer instance; private InstanceContainer instance;
private Chunk chunk; private Chunk chunk;
@ -47,19 +47,30 @@ public class ChunkBatch implements BlockModifier {
this.dataList.add(data); this.dataList.add(data);
} }
public void flush(Consumer<Chunk> callback) { public void flushChunkGenerator(ChunkGenerator chunkGenerator, Consumer<Chunk> callback) {
synchronized (chunk) { batchesPool.execute(() -> {
batchesPool.execute(() -> { chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ());
for (BlockData data : dataList) { singleThreadFlush(callback);
data.apply(chunk); });
} }
// dataList.clear(); public void flush(Consumer<Chunk> callback) {
chunk.refreshDataPacket(); batchesPool.execute(() -> {
instance.sendChunkUpdate(chunk); singleThreadFlush(callback);
if (callback != null) });
callback.accept(chunk); }
});
private void singleThreadFlush(Consumer<Chunk> callback) {
synchronized (chunk) {
for (BlockData data : dataList) {
data.apply(chunk);
}
// dataList.clear();
chunk.refreshDataPacket();
instance.sendChunkUpdate(chunk);
if (callback != null)
callback.accept(chunk);
} }
} }

View File

@ -1,21 +1,15 @@
package fr.themode.minestom.instance; package fr.themode.minestom.instance;
import fr.themode.minestom.Main; import fr.themode.minestom.io.IOManager;
import fr.themode.minestom.utils.CompressionUtils; import fr.themode.minestom.utils.CompressionUtils;
import fr.themode.minestom.utils.SerializerUtils; import fr.themode.minestom.utils.SerializerUtils;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer; import java.util.function.Consumer;
public class ChunkLoaderIO { public class ChunkLoaderIO {
private static final int COMPRESSION_LEVEL = 1;
private ExecutorService chunkLoaderPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_IO);
private static File getChunkFile(int chunkX, int chunkZ, File folder) { private static File getChunkFile(int chunkX, int chunkZ, File folder) {
return new File(folder, getChunkFileName(chunkX, chunkZ)); return new File(folder, getChunkFileName(chunkX, chunkZ));
} }
@ -25,7 +19,7 @@ public class ChunkLoaderIO {
} }
protected void saveChunk(Chunk chunk, File folder, Runnable callback) { protected void saveChunk(Chunk chunk, File folder, Runnable callback) {
chunkLoaderPool.execute(() -> { IOManager.submit(() -> {
File chunkFile = getChunkFile(chunk.getChunkX(), chunk.getChunkZ(), folder); File chunkFile = getChunkFile(chunk.getChunkX(), chunk.getChunkZ(), folder);
try (FileOutputStream fos = new FileOutputStream(chunkFile)) { try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
byte[] data = chunk.getSerializedData(); byte[] data = chunk.getSerializedData();
@ -42,7 +36,7 @@ public class ChunkLoaderIO {
} }
protected void loadChunk(int chunkX, int chunkZ, Instance instance, Consumer<Chunk> callback) { protected void loadChunk(int chunkX, int chunkZ, Instance instance, Consumer<Chunk> callback) {
chunkLoaderPool.execute(() -> { IOManager.submit(() -> {
File chunkFile = getChunkFile(chunkX, chunkZ, instance.getFolder()); File chunkFile = getChunkFile(chunkX, chunkZ, instance.getFolder());
if (!chunkFile.exists()) { if (!chunkFile.exists()) {
instance.createChunk(chunkX, chunkZ, callback); // Chunk file does not exist, create new chunk instance.createChunk(chunkX, chunkZ, callback); // Chunk file does not exist, create new chunk

View File

@ -185,8 +185,10 @@ public abstract class Instance implements BlockModifier {
// Send all visible entities // Send all visible entities
for (long chunkIndex : visibleChunksEntity) { for (long chunkIndex : visibleChunksEntity) {
getEntitiesInChunk(chunkIndex).forEach(ent -> { getEntitiesInChunk(chunkIndex).forEach(ent -> {
if (isPlayer) if (isPlayer) {
ent.addViewer((Player) entity); ent.addViewer((Player) entity);
}
if (ent instanceof Player) { if (ent instanceof Player) {
entity.addViewer((Player) ent); entity.addViewer((Player) ent);
} }
@ -249,6 +251,7 @@ public abstract class Instance implements BlockModifier {
} }
private Set<Entity> getEntitiesInChunk(long index) { private Set<Entity> getEntitiesInChunk(long index) {
return chunkEntities.getOrDefault(index, new CopyOnWriteArraySet<>()); Set<Entity> entities = chunkEntities.get(index);
return entities != null ? entities : new CopyOnWriteArraySet<>();
} }
} }

View File

@ -175,8 +175,7 @@ public class InstanceContainer extends Instance {
cacheChunk(chunk); cacheChunk(chunk);
if (chunkGenerator != null) { if (chunkGenerator != null) {
ChunkBatch chunkBatch = createChunkBatch(chunk); ChunkBatch chunkBatch = createChunkBatch(chunk);
chunkGenerator.generateChunkData(chunkBatch, chunkX, chunkZ); chunkBatch.flushChunkGenerator(chunkGenerator, callback);
chunkBatch.flush(callback);
} }
} }
@ -198,6 +197,7 @@ public class InstanceContainer extends Instance {
public void sendChunk(Player player, Chunk chunk) { public void sendChunk(Player player, Chunk chunk) {
Packet data = chunk.getFullDataPacket(); Packet data = chunk.getFullDataPacket();
if (data == null || !chunk.packetUpdated) { if (data == null || !chunk.packetUpdated) {
System.out.println("UPDATE CHUNK");
PacketWriterUtils.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { PacketWriterUtils.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer); chunk.setFullDataPacket(buffer);
chunk.packetUpdated = true; chunk.packetUpdated = true;

View File

@ -0,0 +1,16 @@
package fr.themode.minestom.io;
import fr.themode.minestom.Main;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.util.concurrent.ExecutorService;
public class IOManager {
private static final ExecutorService IO_POOL = new MinestomThread(Main.THREAD_COUNT_IO, "Ms-IOPool");
public static void submit(Runnable runnable) {
IO_POOL.execute(runnable);
}
}

View File

@ -48,7 +48,6 @@ public class PlayerDiggingListener {
} }
break; break;
case CANCELLED_DIGGING: case CANCELLED_DIGGING:
player.sendBlockBreakAnimation(blockPosition, (byte) -1);
player.resetTargetBlock(); player.resetTargetBlock();
removeEffect(player); removeEffect(player);
break; break;

View File

@ -6,15 +6,15 @@ import fr.themode.minestom.entity.Player;
import fr.themode.minestom.net.packet.server.ServerPacket; import fr.themode.minestom.net.packet.server.ServerPacket;
import fr.themode.minestom.net.player.PlayerConnection; import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.utils.PacketUtils; import fr.themode.minestom.utils.PacketUtils;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer; import java.util.function.Consumer;
public class PacketWriterUtils { public class PacketWriterUtils {
private static ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PACKET_WRITER); private static ExecutorService batchesPool = new MinestomThread(Main.THREAD_COUNT_PACKET_WRITER, "Ms-PacketWriterPool");
public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Packet> consumer) { public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Packet> consumer) {
batchesPool.execute(() -> { batchesPool.execute(() -> {

View File

@ -34,9 +34,9 @@ public class SpawnMobPacket implements ServerPacket {
writer.writeShort(velocityZ); writer.writeShort(velocityZ);
if (consumer != null) { if (consumer != null) {
writer.write(consumer); writer.write(consumer);
} else {
writer.writeByte((byte) 0xff);
} }
writer.writeByte((byte) 0xff);
} }
@Override @Override

View File

@ -23,7 +23,6 @@ public class ChunkUtils {
} }
public static long[] getChunksInRange(final Position position, int range) { public static long[] getChunksInRange(final Position position, int range) {
long[] visibleChunks = new long[MathUtils.square(range + 1)]; long[] visibleChunks = new long[MathUtils.square(range + 1)];
final int startLoop = -(range / 2); final int startLoop = -(range / 2);
final int endLoop = range / 2 + 1; final int endLoop = range / 2 + 1;

View File

@ -32,8 +32,13 @@ public class EntityUtils {
if (instance == null) if (instance == null)
return false; return false;
Position position = entity.getPosition(); Position position = entity.getPosition();
short blockId = instance.getBlockId(position.toBlockPosition().subtract(0, 1, 0)); try {
return blockId != 0; short blockId = instance.getBlockId(position.toBlockPosition().subtract(0, 1, 0));
return blockId != 0;
} catch (NullPointerException e) {
// Probably an entity at the border of an unloaded chunk
return false;
}
} }
} }

View File

@ -10,7 +10,6 @@ public class PacketUtils {
public static void writePacket(ServerPacket serverPacket, Consumer<Packet> callback) { public static void writePacket(ServerPacket serverPacket, Consumer<Packet> callback) {
int id = serverPacket.getId(); int id = serverPacket.getId();
//System.out.println("SEND PACKET: 0x"+Integer.toHexString(id));
Packet packet = Packet.builder(); Packet packet = Packet.builder();
Utils.writeVarInt(packet, id); Utils.writeVarInt(packet, id);
PacketWriter packetWriter = new PacketWriter(packet); PacketWriter packetWriter = new PacketWriter(packet);

View File

@ -0,0 +1,25 @@
package fr.themode.minestom.utils;
public class PrimitiveConversion {
public static Class getObjectClass(Class clazz) {
if (clazz == boolean.class)
return Boolean.class;
if (clazz == byte.class)
return Byte.class;
if (clazz == char.class)
return Character.class;
if (clazz == short.class)
return Short.class;
if (clazz == int.class)
return Integer.class;
if (clazz == long.class)
return Long.class;
if (clazz == float.class)
return Float.class;
if (clazz == double.class)
return Double.class;
return clazz;
}
}

View File

@ -0,0 +1,16 @@
package fr.themode.minestom.utils.thread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MinestomThread extends ThreadPoolExecutor {
public MinestomThread(int nThreads, String name) {
super(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> {
Thread thread = new Thread(r);
thread.setName(thread.getName().replace("Thread", name));
return thread;
});
}
}