Bugs fixes & scheduled task API

This commit is contained in:
TheMode 2019-09-23 19:56:08 +02:00
parent 5c0056e183
commit c66020a196
17 changed files with 320 additions and 69 deletions

View File

@ -17,19 +17,23 @@ import fr.themode.minestom.net.packet.client.status.LegacyServerListPingPacket;
import fr.themode.minestom.net.packet.server.play.KeepAlivePacket;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.scoreboard.TeamManager;
import fr.themode.minestom.timer.SchedulerManager;
import fr.themode.minestom.utils.Utils;
public class Main {
// Thread number
// Thread pools
public static final int THREAD_COUNT_PACKET_WRITER = 2;
public static final int THREAD_COUNT_IO = 2;
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
public static final int THREAD_COUNT_BLOCK_UPDATE = 2;
public static final int THREAD_COUNT_ENTITIES = 2;
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;
public static final int THREAD_COUNT_SCHEDULER = 2;
// Can be modified at performance cost when decreased
public static final int TICK_MS = 50;
public static final int TICK_PER_SECOND = 1000 / TICK_MS;
// Config
@ -48,6 +52,7 @@ public class Main {
private static EntityManager entityManager;
private static DataManager dataManager;
private static TeamManager teamManager;
private static SchedulerManager schedulerManager;
public static void main(String[] args) {
connectionManager = new ConnectionManager();
@ -59,6 +64,7 @@ public class Main {
entityManager = new EntityManager();
dataManager = new DataManager();
teamManager = new TeamManager();
schedulerManager = new SchedulerManager();
blockManager.registerBlock(new StoneBlock());
blockManager.registerBlock(new UpdatableBlockDemo());
@ -123,6 +129,9 @@ public class Main {
// Blocks update
instanceManager.updateBlocks();
// Scheduler
schedulerManager.update();
// TODO miscellaneous update (scoreboard)
// Sleep until next tick
@ -168,6 +177,10 @@ public class Main {
return teamManager;
}
public static SchedulerManager getSchedulerManager() {
return schedulerManager;
}
public static ConnectionManager getConnectionManager() {
return connectionManager;
}

View File

@ -90,6 +90,14 @@ public class Player extends LivingEntity {
private Team team;
private BelowNameScoreboard belowNameScoreboard;
// Abilities
private boolean invulnerable;
private boolean flying;
private boolean allowFlying;
private boolean instantBreak;
private float flyingSpeed = 0.05f;
private float fieldViewModifier = 0.1f;
// Vehicle
private float sideways;
private float forward;
@ -189,7 +197,9 @@ public class Player extends LivingEntity {
scoreboard.addViewer(this);
scoreboard.updateLineContent("id3", "I HAVE BEEN UPDATED &2TEST");*/
setBelowNameScoreboard(new BelowNameScoreboard());
BelowNameScoreboard belowNameScoreboard = new BelowNameScoreboard();
setBelowNameScoreboard(belowNameScoreboard);
belowNameScoreboard.updateScore(this, 50);
});
}
@ -718,6 +728,7 @@ public class Player extends LivingEntity {
this.belowNameScoreboard = belowNameScoreboard;
if (belowNameScoreboard != null) {
belowNameScoreboard.addViewer(this);
belowNameScoreboard.displayScoreboard(this);
getViewers().forEach(player -> belowNameScoreboard.addViewer(player));
}
}
@ -824,6 +835,72 @@ public class Player extends LivingEntity {
playerConnection.sendPacket(positionAndLookPacket);
}
public boolean isInvulnerable() {
return invulnerable;
}
public void setInvulnerable(boolean invulnerable) {
this.invulnerable = invulnerable;
refreshAbilities();
}
public boolean isFlying() {
return flying;
}
public void setFlying(boolean flying) {
this.flying = flying;
refreshAbilities();
}
public boolean isAllowFlying() {
return allowFlying;
}
public void setAllowFlying(boolean allowFlying) {
this.allowFlying = allowFlying;
refreshAbilities();
}
public boolean isInstantBreak() {
return instantBreak;
}
public void setInstantBreak(boolean instantBreak) {
this.instantBreak = instantBreak;
refreshAbilities();
}
public float getFlyingSpeed() {
return flyingSpeed;
}
public void setFlyingSpeed(float flyingSpeed) {
this.flyingSpeed = flyingSpeed;
refreshAbilities();
}
public float getFieldViewModifier() {
return fieldViewModifier;
}
public void setFieldViewModifier(float fieldViewModifier) {
this.fieldViewModifier = fieldViewModifier;
refreshAbilities();
}
private void refreshAbilities() {
PlayerAbilitiesPacket playerAbilitiesPacket = new PlayerAbilitiesPacket();
playerAbilitiesPacket.invulnerable = invulnerable;
playerAbilitiesPacket.flying = flying;
playerAbilitiesPacket.allowFlying = allowFlying;
playerAbilitiesPacket.instantBreak = instantBreak;
playerAbilitiesPacket.flyingSpeed = flyingSpeed;
playerAbilitiesPacket.fieldViewModifier = fieldViewModifier;
playerConnection.sendPacket(playerAbilitiesPacket);
}
public void addPacketToQueue(ClientPlayPacket packet) {
this.packets.add(packet);
}

View File

@ -8,12 +8,12 @@ import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.instance.block.UpdateConsumer;
import fr.themode.minestom.instance.block.UpdateOption;
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.PacketUtils;
import fr.themode.minestom.utils.SerializerUtils;
import fr.themode.minestom.utils.time.CooldownUtils;
import fr.themode.minestom.utils.time.UpdateOption;
import it.unimi.dsi.fastutil.ints.*;
import java.io.ByteArrayOutputStream;
@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
// TODO light data & API
public class Chunk implements Viewable {
private static final BlockManager BLOCK_MANAGER = Main.getBlockManager();
@ -40,7 +41,7 @@ public class Chunk implements Viewable {
// Used to get all blocks with data (no null)
// Key is still chunk coord
// TODO shouldn't take Data object (too much memory overhead)
// FIXME: shouldn't take Data object (too much memory overhead)
private Int2ObjectMap<Data> blocksData = new Int2ObjectOpenHashMap<>(16 * 16); // Start with the size of a single row
// Contains CustomBlocks' index which are updatable
@ -53,8 +54,6 @@ public class Chunk implements Viewable {
// Block entities
private Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
// TODO blocks update
// Cache
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private Packet fullDataPacket;
@ -92,12 +91,24 @@ public class Chunk implements Viewable {
private void setBlock(byte x, byte y, byte z, short blockType, short customId, Data data, UpdateConsumer updateConsumer) {
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
if (blockType != 0 || customId != 0) {
int value = (blockType << 16 | customId & 0xFFFF); // Merge blockType and customId to one unique Integer (16/16)
if (blockType != 0
|| (blockType == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found
int value = (blockType << 16 | customId & 0xFFFF); // Merge blockType and customId to one unique Integer (16/16 bits)
this.blocks.put(index, value);
} else {
// Block has been deleted
// Block has been deleted, clear cache and return
this.blocks.remove(index);
this.blocksData.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
this.packetUpdated = false;
return;
}
// Set the new data (or remove from the map if is null)
@ -173,11 +184,7 @@ public class Chunk implements Viewable {
IntIterator iterator = new IntOpenHashSet(updatableBlocks).iterator();
while (iterator.hasNext()) {
int index = iterator.nextInt();
byte[] blockPos = SerializerUtils.indexToChunkPosition(index);
byte x = blockPos[0];
byte y = blockPos[1];
byte z = blockPos[2];
CustomBlock customBlock = getCustomBlock(x, y, z);
CustomBlock customBlock = getCustomBlock(index);
// Update cooldown
UpdateOption updateOption = customBlock.getUpdateOption();
@ -187,6 +194,12 @@ public class Chunk implements Viewable {
continue;
this.updatableBlocksLastUpdate.put(index, time); // Refresh last update time
byte[] blockPos = SerializerUtils.indexToChunkPosition(index);
byte x = blockPos[0];
byte y = blockPos[1];
byte z = blockPos[2];
BlockPosition blockPosition = new BlockPosition(x + 16 * chunkX, y, z + 16 * chunkZ);
Data data = getData(index);
customBlock.update(instance, blockPosition, data);

View File

@ -1,7 +1,6 @@
package fr.themode.minestom.instance.batch;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.instance.BlockModifier;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.InstanceContainer;
@ -10,7 +9,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BlockBatch implements IBatch, BlockModifier {
public class BlockBatch implements IBatch {
private InstanceContainer instance;
@ -21,38 +20,42 @@ public class BlockBatch implements IBatch, BlockModifier {
}
@Override
public synchronized void setBlock(int x, int y, int z, short blockId, Data data) {
Chunk chunk = this.instance.getChunkAt(x, z);
List<BlockData> blocksData = this.data.getOrDefault(chunk, new ArrayList<>());
public void setBlock(int x, int y, int z, short blockId, Data data) {
synchronized (this) {
Chunk chunk = this.instance.getChunkAt(x, z);
List<BlockData> blocksData = this.data.getOrDefault(chunk, new ArrayList<>());
BlockData blockData = new BlockData();
blockData.x = x % 16;
blockData.y = y;
blockData.z = z % 16;
blockData.blockId = blockId;
blockData.data = data;
BlockData blockData = new BlockData();
blockData.x = x % 16;
blockData.y = y;
blockData.z = z % 16;
blockData.blockId = blockId;
blockData.data = data;
blocksData.add(blockData);
blocksData.add(blockData);
this.data.put(chunk, blocksData);
this.data.put(chunk, blocksData);
}
}
@Override
public void setCustomBlock(int x, int y, int z, short blockId, Data data) {
Chunk chunk = this.instance.getChunkAt(x, z);
List<BlockData> blocksData = this.data.getOrDefault(chunk, new ArrayList<>());
synchronized (this) {
Chunk chunk = this.instance.getChunkAt(x, z);
List<BlockData> blocksData = this.data.getOrDefault(chunk, new ArrayList<>());
BlockData blockData = new BlockData();
blockData.x = x % 16;
blockData.y = y;
blockData.z = z % 16;
blockData.isCustomBlock = true;
blockData.blockId = blockId;
blockData.data = data;
BlockData blockData = new BlockData();
blockData.x = x % 16;
blockData.y = y;
blockData.z = z % 16;
blockData.isCustomBlock = true;
blockData.blockId = blockId;
blockData.data = data;
blocksData.add(blockData);
blocksData.add(blockData);
this.data.put(chunk, blocksData);
this.data.put(chunk, blocksData);
}
}
public void flush(Runnable callback) {

View File

@ -1,24 +1,24 @@
package fr.themode.minestom.instance.batch;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.instance.BlockModifier;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.ChunkGenerator;
import fr.themode.minestom.instance.InstanceContainer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* Use chunk coordinate (0-16) instead of world's
*/
public class ChunkBatch implements IBatch, BlockModifier {
public class ChunkBatch implements IBatch {
private InstanceContainer instance;
private Chunk chunk;
private List<BlockData> dataList = new ArrayList<>();
private List<BlockData> dataList = Collections.synchronizedList(new ArrayList<>());
public ChunkBatch(InstanceContainer instance, Chunk chunk) {
this.instance = instance;

View File

@ -1,11 +1,12 @@
package fr.themode.minestom.instance.batch;
import fr.themode.minestom.Main;
import fr.themode.minestom.instance.BlockModifier;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.util.concurrent.ExecutorService;
public interface IBatch {
public interface IBatch extends BlockModifier {
ExecutorService batchesPool = new MinestomThread(Main.THREAD_COUNT_BLOCK_BATCH, "Ms-BlockBatchPool");

View File

@ -4,6 +4,7 @@ import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.time.UpdateOption;
import java.util.concurrent.atomic.AtomicInteger;

View File

@ -2,7 +2,7 @@ package fr.themode.minestom.instance.demo;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.instance.block.UpdateOption;
import fr.themode.minestom.utils.time.UpdateOption;
public class StoneBlock extends CustomBlock {

View File

@ -4,9 +4,9 @@ import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.instance.block.UpdateOption;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.time.TimeUnit;
import fr.themode.minestom.utils.time.UpdateOption;
public class UpdatableBlockDemo extends CustomBlock {

View File

@ -19,7 +19,8 @@ public class ClientPacketsHandler {
ConstructorAccess<? extends ClientPacket> constructorAccess = constructorAccesses[id];
if (constructorAccess == null)
System.err.println("Packet id 0x" + Integer.toHexString(id) + " isn't registered!");
throw new IllegalStateException("Packet id 0x" + Integer.toHexString(id) + " isn't registered!");
ClientPacket packet = constructorAccess.newInstance();
return packet;
}

View File

@ -18,22 +18,32 @@ public class ClientUseEntityPacket extends ClientPlayPacket {
reader.readVarInt(value -> targetId = value);
reader.readVarInt(value -> {
type = Type.values()[value];
if (type == Type.ATTACK)
callback.run();
});
if (this.type == Type.INTERACT_AT) {
reader.readFloat(value -> x = value);
reader.readFloat(value -> y = value);
reader.readFloat(value -> {
z = value;
});
}
if (type == Type.INTERACT || type == Type.INTERACT_AT)
reader.readVarInt(value -> {
hand = Player.Hand.values()[value];
callback.run();
});
switch (type) {
case ATTACK:
callback.run();
break;
case INTERACT:
reader.readVarInt(v2 -> {
hand = Player.Hand.values()[v2];
callback.run();
});
break;
case INTERACT_AT:
reader.readFloat(vX -> x = vX);
reader.readFloat(vY -> y = vY);
reader.readFloat(vZ -> z = vZ);
reader.readVarInt(v2 -> {
hand = Player.Hand.values()[v2];
callback.run();
});
break;
}
});
}
public enum Type {

View File

@ -0,0 +1,39 @@
package fr.themode.minestom.net.packet.server.play;
import fr.themode.minestom.net.packet.PacketWriter;
import fr.themode.minestom.net.packet.server.ServerPacket;
public class PlayerAbilitiesPacket implements ServerPacket {
// Flags
public boolean invulnerable;
public boolean flying;
public boolean allowFlying;
public boolean instantBreak;
// Options
public float flyingSpeed;
public float fieldViewModifier;
@Override
public void write(PacketWriter writer) {
byte flags = 0;
if (invulnerable)
flags += 1;
if (flying)
flags += 2;
if (allowFlying)
flags += 4;
if (instantBreak)
flags += 8;
writer.writeByte(flags);
writer.writeFloat(flyingSpeed);
writer.writeFloat(fieldViewModifier);
}
@Override
public int getId() {
return 0x31;
}
}

View File

@ -4,6 +4,7 @@ import fr.themode.minestom.Viewable;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.net.packet.server.play.DisplayScoreboardPacket;
import fr.themode.minestom.net.packet.server.play.ScoreboardObjectivePacket;
import fr.themode.minestom.net.packet.server.play.UpdateScorePacket;
import fr.themode.minestom.net.player.PlayerConnection;
import java.util.Collections;
@ -11,6 +12,7 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
// TODO fix score and objective refresh
public class BelowNameScoreboard implements Viewable {
private static final AtomicInteger counter = new AtomicInteger();
@ -28,7 +30,7 @@ public class BelowNameScoreboard implements Viewable {
public BelowNameScoreboard() {
this.objectiveName = SCOREBOARD_PREFIX + counter.incrementAndGet();
System.out.println("DEBUG: " + objectiveName);
scoreboardObjectivePacket = new ScoreboardObjectivePacket();
scoreboardObjectivePacket.objectiveName = objectiveName;
scoreboardObjectivePacket.mode = 0;
@ -36,17 +38,25 @@ public class BelowNameScoreboard implements Viewable {
scoreboardObjectivePacket.type = 0;
displayScoreboardPacket = new DisplayScoreboardPacket();
displayScoreboardPacket.position = 2;
displayScoreboardPacket.position = 2; // Below name
displayScoreboardPacket.scoreName = objectiveName;
}
public void updateScore(Player player, int score) {
UpdateScorePacket updateScorePacket = new UpdateScorePacket();
updateScorePacket.entityName = player.getUsername();
updateScorePacket.action = 0; // Create/update
updateScorePacket.objectiveName = objectiveName;
updateScorePacket.value = score;
sendPacketToViewers(updateScorePacket);
}
@Override
public void addViewer(Player player) {
this.viewers.add(player);
PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(scoreboardObjectivePacket);
// TODO score
playerConnection.sendPacket(displayScoreboardPacket);
}
@Override
@ -58,4 +68,9 @@ public class BelowNameScoreboard implements Viewable {
public Set<Player> getViewers() {
return Collections.unmodifiableSet(viewers);
}
public void displayScoreboard(Player player) {
PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(displayScoreboardPacket);
}
}

View File

@ -1,11 +1,43 @@
package fr.themode.minestom.timer;
import fr.themode.minestom.utils.time.TimeUnit;
import fr.themode.minestom.Main;
import fr.themode.minestom.utils.thread.MinestomThread;
import fr.themode.minestom.utils.time.CooldownUtils;
import fr.themode.minestom.utils.time.UpdateOption;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
public class SchedulerManager {
public void addRepeatingTask(Runnable runnable, TimeUnit timeUnit, int time) {
private static final AtomicInteger COUNTER = new AtomicInteger();
private static ExecutorService batchesPool = new MinestomThread(Main.THREAD_COUNT_SCHEDULER, "Ms-SchedulerPool");
private List<Task> tasks = new CopyOnWriteArrayList<>();
public void addRepeatingTask(TaskRunnable runnable, UpdateOption updateOption) {
runnable.setId(COUNTER.incrementAndGet());
Task task = new Task(runnable, updateOption);
this.tasks.add(task);
}
public void update() {
long time = System.currentTimeMillis();
batchesPool.execute(() -> {
for (Task task : tasks) {
UpdateOption updateOption = task.getUpdateOption();
long lastUpdate = task.getLastUpdateTime();
boolean hasCooldown = CooldownUtils.hasCooldown(time, lastUpdate, updateOption.getTimeUnit(), updateOption.getValue());
if (!hasCooldown) {
TaskRunnable runnable = task.getRunnable();
runnable.run();
task.refreshLastUpdateTime(time);
}
}
});
}
}

View File

@ -0,0 +1,32 @@
package fr.themode.minestom.timer;
import fr.themode.minestom.utils.time.UpdateOption;
public class Task {
private TaskRunnable runnable;
private UpdateOption updateOption;
private long lastUpdateTime;
public Task(TaskRunnable runnable, UpdateOption updateOption) {
this.runnable = runnable;
this.updateOption = updateOption;
}
protected void refreshLastUpdateTime(long lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
protected long getLastUpdateTime() {
return lastUpdateTime;
}
public TaskRunnable getRunnable() {
return runnable;
}
public UpdateOption getUpdateOption() {
return updateOption;
}
}

View File

@ -0,0 +1,16 @@
package fr.themode.minestom.timer;
public abstract class TaskRunnable {
private int id;
public abstract void run();
public int getId() {
return id;
}
protected void setId(int id) {
this.id = id;
}
}

View File

@ -1,6 +1,4 @@
package fr.themode.minestom.instance.block;
import fr.themode.minestom.utils.time.TimeUnit;
package fr.themode.minestom.utils.time;
public class UpdateOption {