Implemented new syncing system

This commit is contained in:
Anuken 2018-01-14 20:27:10 -05:00
parent e545b7cca7
commit cc45e5afca
16 changed files with 237 additions and 191 deletions

View file

@ -21,7 +21,7 @@ allprojects {
appName = "Mindustry"
gdxVersion = '1.9.8'
aiVersion = '1.8.1'
uCoreVersion = '89fa665';
uCoreVersion = '7a567c6';
}
repositories {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 301 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Before After
Before After

View file

@ -9,6 +9,7 @@ import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.mindustry.graphics.Fx;
@ -16,8 +17,6 @@ import io.anuke.mindustry.io.NetworkIO;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.net.Syncable;
import io.anuke.mindustry.net.Syncable.Interpolator;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.resource.Upgrade;
@ -30,10 +29,12 @@ import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.BaseBulletType;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.Entity;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.modules.Module;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.ui;
@ -113,33 +114,30 @@ public class NetClient extends Module {
Net.handle(SyncPacket.class, packet -> {
if(!gotData) return;
//TODO awful code
for(int i = 0; i < packet.ids.length; i ++){
int id = packet.ids[i];
if(id != Vars.player.id){
Entity entity;
if(i >= packet.enemyStart){
entity = Vars.control.enemyGroup.getByID(id);
}else {
entity = Vars.control.playerGroup.getByID(id);
ByteBuffer data = ByteBuffer.wrap(packet.data);
byte groupid = data.get();
EntityGroup<?> group = Entities.getGroup(groupid);
while(data.position() < data.capacity()){
int id = data.getInt();
SyncEntity entity = (SyncEntity) group.getByID(id);
if (entity == null || id == Vars.player.id) {
if (!requests.contains(id) && id != Vars.player.id) {
UCore.log("Requesting entity " + id, "group " + group.getType());
requests.add(id);
EntityRequestPacket req = new EntityRequestPacket();
req.id = id;
Net.send(req, SendMode.tcp);
}
Syncable sync = ((Syncable)entity);
if(sync == null){
if(!requests.contains(id)){
requests.add(id);
Gdx.app.error("Mindustry", "Sending entity request: " + id);
EntityRequestPacket req = new EntityRequestPacket();
req.id = id;
Net.send(req, SendMode.tcp);
}
continue;
}
//augh
((Interpolator)sync.getInterpolator()).type.read(entity, packet.data[i]);
data.position(data.position() + SyncEntity.getWriteSize((Class<? extends SyncEntity>) group.getType()));
} else {
entity.read(data);
}
}
});
@ -281,8 +279,8 @@ public class NetClient extends Module {
recieved.contains(player.id)) return;
recieved.add(player.id);
player.getInterpolator().last.set(player.x, player.y);
player.getInterpolator().target.set(player.x, player.y);
player.interpolator.last.set(player.x, player.y);
player.interpolator.target.set(player.x, player.y);
player.add();
});
});
@ -412,8 +410,12 @@ public class NetClient extends Module {
void sync(){
if(Timers.get("syncPlayer", playerSyncTime)){
byte[] bytes = new byte[Vars.player.getWriteSize()];
ByteBuffer buffer = ByteBuffer.wrap(bytes);
Vars.player.write(buffer);
PositionPacket packet = new PositionPacket();
packet.data = Vars.player.getInterpolator().type.write(Vars.player);
packet.data = bytes;
Net.send(packet, SendMode.udp);
}

View file

@ -6,6 +6,7 @@ import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.io.NetworkIO;
@ -17,7 +18,9 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.UCore;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.Entity;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Bundles;
import io.anuke.ucore.util.Mathf;
@ -26,6 +29,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class NetServer extends Module{
/**Maps connection IDs to players.*/
@ -49,8 +53,8 @@ public class NetServer extends Module{
player.name = packet.name;
player.isAndroid = packet.android;
player.set(Vars.control.core.worldx(), Vars.control.core.worldy() - Vars.tilesize*2);
player.getInterpolator().last.set(player.x, player.y);
player.getInterpolator().target.set(player.x, player.y);
player.interpolator.last.set(player.x, player.y);
player.interpolator.target.set(player.x, player.y);
connections.put(id, player);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
@ -95,7 +99,7 @@ public class NetServer extends Module{
Net.handleServer(PositionPacket.class, pos -> {
Player player = connections.get(Net.getLastConnection());
player.getInterpolator().type.read(player, pos.data);
player.read(ByteBuffer.wrap(pos.data));
});
Net.handleServer(ShootPacket.class, packet -> {
@ -270,32 +274,71 @@ public class NetServer extends Module{
void sync(){
if(Timers.get("serverSync", serverSyncTime)){
SyncPacket packet = new SyncPacket();
int amount = Vars.control.playerGroup.amount() + Vars.control.enemyGroup.amount();
packet.ids = new int[amount];
packet.data = new float[amount][0];
//scan through all groups with syncable entities
for(EntityGroup<?> group : Entities.getAllGroups()) {
if(group.amount() == 0 || !(group.all().first() instanceof SyncEntity)) continue;
short index = 0;
UCore.log("Writing group " + group.getType(), "amount: " + group.amount());
for(Player player : Vars.control.playerGroup.all()){
float[] out = player.getInterpolator().type.write(player);
packet.data[index] = out;
packet.ids[index] = player.id;
//get write size for one entity (adding 4, as you need to write the ID as well)
int writesize = SyncEntity.getWriteSize((Class<? extends SyncEntity>)group.getType()) + 4;
//amount of entities
int amount = group.amount();
//maximum amount of entities per packet
int maxsize = 64;
index ++;
//current buffer you're writing to
ByteBuffer current = null;
//number of entities written to this packet/buffer
int written = 0;
//for all the entities...
for (int i = 0; i < amount; i++) {
//if the buffer is null, create a new one
if(current == null){
//calculate amount of entities to go into this packet
int csize = Math.min(amount-i, maxsize);
//create a byte array to write to
byte[] bytes = new byte[csize*writesize + 1];
//wrap it for easy writing
current = ByteBuffer.wrap(bytes);
//write the group ID so the client knows which group this is
current.put((byte)group.getID());
UCore.log(" Writing new packet: " + i);
}
SyncEntity entity = (SyncEntity) group.all().get(i);
UCore.log("Writing entity: " + entity.id);
//write ID to the buffer
current.putInt(entity.id);
int previous = current.position();
//write extra data to the buffer
entity.write(current);
written ++;
//if the packet is too big now...
if(written >= maxsize){
//send the packet.
SyncPacket packet = new SyncPacket();
packet.data = current.array();
Net.send(packet, SendMode.udp);
//reset data, send the next packet
current = null;
written = 0;
}
}
//make sure to send incomplete packets too
if(current != null){
SyncPacket packet = new SyncPacket();
packet.data = current.array();
Net.send(packet, SendMode.udp);
}
}
packet.enemyStart = index;
for(Enemy enemy : Vars.control.enemyGroup.all()){
float[] out = enemy.getInterpolator().type.write(enemy);
packet.data[index] = out;
packet.ids[index] = enemy.id;
index ++;
}
Net.send(packet, SendMode.udp);
}
if(Timers.get("serverItemSync", itemSyncTime)){

View file

@ -100,15 +100,8 @@ public class Renderer extends RendererModule{
clearScreen();
}else{
boolean smoothcam = Settings.getBool("smoothcam");
//TODO identify the source of this bug
if(control.core == null){
// ui.showError("$text.error.crashmessage");
// GameState.set(State.menu);
// return;
}
if(control.core.block() == ProductionBlocks.core){
if(control.core == null || control.core.block() == ProductionBlocks.core){
if(!smoothcam){
setCamera(player.x, player.y);
}else{

View file

@ -2,20 +2,20 @@ package io.anuke.mindustry.entities;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.net.Syncable;
import io.anuke.mindustry.resource.Mech;
import io.anuke.mindustry.resource.Weapon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.ucore.core.*;
import io.anuke.ucore.entities.DestructibleEntity;
import io.anuke.ucore.entities.SolidEntity;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.*;
public class Player extends DestructibleEntity implements Syncable{
public class Player extends SyncEntity{
static final float speed = 1.1f;
static final float dashSpeed = 1.8f;
@ -33,7 +33,6 @@ public class Player extends DestructibleEntity implements Syncable{
public transient int clientid;
public transient boolean isLocal = false;
public transient Interpolator<Player> inter = new Interpolator<>(SyncType.player);
public Player(){
hitbox.setSize(5);
@ -43,11 +42,6 @@ public class Player extends DestructibleEntity implements Syncable{
heal();
}
@Override
public Interpolator<Player> getInterpolator() {
return inter;
}
@Override
public void damage(int amount){
if(!Vars.debug && !isAndroid)
@ -117,7 +111,7 @@ public class Player extends DestructibleEntity implements Syncable{
@Override
public void update(){
if(!isLocal || isAndroid || Vars.ui.chatfrag.chatOpen()){
if(!isDead() && !isLocal) inter.update(this);
if(!isDead() && !isLocal) interpolate();
return;
}
@ -186,4 +180,49 @@ public class Player extends DestructibleEntity implements Syncable{
public String toString() {
return "Player{" + id + ", android=" + isAndroid + ", local=" + isLocal + ", " + x + ", " + y + "}\n";
}
@Override
public void write(ByteBuffer data) {
data.putFloat(x);
data.putFloat(y);
data.putFloat(angle);
data.putShort((short)health);
data.put((byte)(dashing ? 1 : 0));
}
@Override
public void read(ByteBuffer data) {
float x = data.getFloat();
float y = data.getFloat();
float angle = data.getFloat();
short health = data.getShort();
byte dashing = data.get();
interpolator.target.set(x, y);
interpolator.targetrot = angle;
this.health = health;
this.dashing = dashing == 1;
}
@Override
public void interpolate() {
Interpolator i = interpolator;
if(i.target.dst(x, y) > 16 && !isAndroid){
set(i.target.x, i.target.y);
}
if(isAndroid && i.target.dst(x, y) > 2f && Timers.get(this, "dashfx", 2)){
Angles.translation(angle + 180, 3f);
Effects.effect(Fx.dashsmoke, x + Angles.x(), y + Angles.y());
}
if(dashing && Timers.get(this, "dashfx", 3)){
Angles.translation(angle + 180, 3f);
Effects.effect(Fx.dashsmoke, x + Angles.x(), y + Angles.y());
}
x = Mathf.lerpDelta(x, i.target.x, 0.4f);
y = Mathf.lerpDelta(y, i.target.y, 0.4f);
angle = Mathf.lerpAngDelta(angle, i.targetrot, 0.6f);
}
}

View file

@ -0,0 +1,46 @@
package io.anuke.mindustry.entities;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ObjectIntMap;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.ucore.entities.DestructibleEntity;
import java.nio.ByteBuffer;
public abstract class SyncEntity extends DestructibleEntity{
private static ObjectIntMap<Class<? extends SyncEntity>> writeSizes = new ObjectIntMap<>();
public transient Interpolator interpolator = new Interpolator();
static{
setWriteSize(Enemy.class, 4 + 4 + 2 + 2);
setWriteSize(Player.class, 4 + 4 + 4 + 2 + 1);
}
public abstract void write(ByteBuffer data);
public abstract void read(ByteBuffer data);
public abstract void interpolate();
public int getWriteSize(){
return getWriteSize(getClass());
}
public static int getWriteSize(Class<? extends SyncEntity> type){
int i = writeSizes.get(type, -1);
if(i == -1) throw new RuntimeException("Write size for class \"" + type + "\" is not defined!");
return i;
}
protected static void setWriteSize(Class<? extends SyncEntity> type, int size){
writeSizes.put(type, size);
}
public class Interpolator {
public Vector2 target = new Vector2();
public Vector2 delta = new Vector2();
public Vector2 last = new Vector2();
public float targetrot;
}
}

View file

@ -4,17 +4,17 @@ import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Syncable;
import io.anuke.ucore.entities.DestructibleEntity;
import io.anuke.ucore.entities.Entity;
import io.anuke.ucore.entities.SolidEntity;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Timer;
public class Enemy extends DestructibleEntity implements Syncable{
protected Interpolator<Enemy> inter = new Interpolator<>(SyncType.enemy);
import java.nio.ByteBuffer;
public class Enemy extends SyncEntity {
public final EnemyType type;
public Timer timer = new Timer(5);
@ -92,8 +92,36 @@ public class Enemy extends DestructibleEntity implements Syncable{
}
@Override
public Interpolator<Enemy> getInterpolator() {
return inter;
public void write(ByteBuffer data) {
data.putFloat(x);
data.putFloat(y);
data.putShort((short)(angle*2));
data.putShort((short)health);
}
@Override
public void read(ByteBuffer data) {
float x = data.getFloat();
float y = data.getFloat();
short angle = data.getShort();
short health = data.getShort();
interpolator.target.set(x, y);
interpolator.targetrot = angle/2f;
this.health = health;
}
@Override
public void interpolate() {
Interpolator i = interpolator;
if(i.target.dst(x, y) > 16){
set(i.target.x, i.target.y);
}
x = Mathf.lerpDelta(x, i.target.x, 0.4f);
y = Mathf.lerpDelta(y, i.target.y, 0.4f);
angle = Mathf.lerpAngDelta(angle, i.targetrot, 0.6f);
}
public void shoot(BulletType bullet){

View file

@ -138,7 +138,7 @@ public class EnemyType {
float range = this.range + enemy.tier * 5;
if(Net.client() && Net.active()){
enemy.inter.update(enemy); //TODO? better structure for interpolation
enemy.interpolate(); //TODO? better structure for interpolation
return;
}

View file

@ -18,9 +18,7 @@ public class Packets {
}
public static class SyncPacket{
public int[] ids;
public float[][] data;
public short enemyStart;
public byte[] data;
}
public static class BlockSyncPacket extends Streamable{
@ -48,7 +46,7 @@ public class Packets {
}
public static class PositionPacket{
public float[] data;
public byte[] data;
}
public static class EffectPacket{

View file

@ -1,102 +0,0 @@
package io.anuke.mindustry.net;
import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entity;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
//TODO clean up this mess
public interface Syncable {
public Interpolator<?> getInterpolator();
public abstract class SyncType<T extends Entity>{
public abstract float[] write(T entity);
public abstract void read(T entity, float[] data);
public abstract void update(T entity, Interpolator interpolator);
//TODO write dashing state so particles appear
public static final SyncType<Player> player = new SyncType<Player>() {
@Override
public float[] write(Player entity) {
return new float[]{entity.x, entity.y, entity.angle, entity.health, entity.dashing ? 1f : -1f};
}
@Override
public void read(Player entity, float[] data) {
entity.getInterpolator().target.set(data[0], data[1]);
entity.getInterpolator().targetrot = data[2];
entity.health = (int)data[3];
entity.dashing = data[4] > 0;
}
@Override
public void update(Player entity, Interpolator interpolator) {
Interpolator i = entity.getInterpolator();
if(i.target.dst(entity.x, entity.y) > 16 && !entity.isAndroid){
entity.set(i.target.x, i.target.y);
}
if(entity.isAndroid && i.target.dst(entity.x, entity.y) > 2f && Timers.get(entity, "dashfx", 2)){
Angles.translation(entity.angle + 180, 3f);
Effects.effect(Fx.dashsmoke, entity.x + Angles.x(), entity.y + Angles.y());
}
if(entity.dashing && Timers.get(entity, "dashfx", 3)){
Angles.translation(entity.angle + 180, 3f);
Effects.effect(Fx.dashsmoke, entity.x + Angles.x(), entity.y + Angles.y());
}
entity.x = Mathf.lerpDelta(entity.x, i.target.x, 0.4f);
entity.y = Mathf.lerpDelta(entity.y, i.target.y, 0.4f);
entity.angle = Mathf.lerpAngDelta(entity.angle, i.targetrot, 0.6f);
}
};
public static final SyncType<Enemy> enemy = new SyncType<Enemy>() {
@Override
public float[] write(Enemy entity) {
return new float[]{entity.x, entity.y, entity.angle, entity.health};
}
@Override
public void read(Enemy entity, float[] data) {
entity.getInterpolator().target.set(data[0], data[1]);
entity.getInterpolator().targetrot = data[2];
entity.health = (int)data[3];
}
@Override
public void update(Enemy entity, Interpolator interpolator) {
Interpolator i = entity.getInterpolator();
if(i.target.dst(entity.x, entity.y) > 16){
entity.set(i.target.x, i.target.y);
}
entity.x = Mathf.lerpDelta(entity.x, i.target.x, 0.4f);
entity.y = Mathf.lerpDelta(entity.y, i.target.y, 0.4f);
entity.angle = Mathf.lerpAngDelta(entity.angle, i.targetrot, 0.6f);
}
};
}
class Interpolator<T extends Entity> {
public SyncType<T> type;
public Vector2 target = new Vector2();
public Vector2 last = new Vector2();
public float targetrot;
public Interpolator(SyncType<T> type){
this.type = type;
}
public void update(T entity){
this.type.update(entity, this);
}
}
}

View file

@ -3,7 +3,6 @@ package io.anuke.mindustry.world;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.ObjectIntMap;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.ucore.UCore;
public class BlockLoader {
static final ObjectIntMap<String> defaultMap = map(

View file

@ -52,7 +52,7 @@ public class KryoClient implements ClientProvider{
}
};
client = new Client(8192, 2048*2);
client = new Client(8192, 2048);
client.setDiscoveryHandler(handler);
Listener listener = new Listener(){

View file

@ -9,7 +9,7 @@ import java.net.InetAddress;
import java.nio.ByteBuffer;
public class KryoRegistrator {
public static boolean fakeLag = true;
public static boolean fakeLag = false;
public static final int fakeLagAmount = 500;
public static void register(Kryo kryo){

View file

@ -30,7 +30,7 @@ public class KryoServer implements ServerProvider {
IntArray connections = new IntArray();
public KryoServer(){
server = new Server(4096*2, 2048*2); //TODO tweak
server = new Server(4096*2, 2048); //TODO tweak
server.setDiscoveryHandler((datagramChannel, fromAddress) -> {
ByteBuffer buffer = KryoRegistrator.writeServerData();
UCore.log("Replying to discover request with buffer of size " + buffer.capacity());