Removed delta compression / Removed client snapshot packet

This commit is contained in:
Anuken
2018-09-08 16:29:09 -04:00
parent 778069c15d
commit 8dbdbe6d6c
10 changed files with 196 additions and 329 deletions

View File

@@ -3,16 +3,15 @@ package io.anuke.mindustry.core;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Base64Coder; import com.badlogic.gdx.utils.Base64Coder;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.IntMap.Entry;
import com.badlogic.gdx.utils.IntSet; import com.badlogic.gdx.utils.IntSet;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.annotations.Annotations.PacketPriority; import io.anuke.annotations.Annotations.PacketPriority;
import io.anuke.annotations.Annotations.Remote; import io.anuke.annotations.Annotations.Remote;
import io.anuke.annotations.Annotations.Variant; import io.anuke.annotations.Annotations.Variant;
import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.SyncTrait; import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.traits.TypeTrait; import io.anuke.mindustry.entities.traits.TypeTrait;
import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.Call;
@@ -23,21 +22,19 @@ import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.net.TraceInfo; import io.anuke.mindustry.net.TraceInfo;
import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.modules.InventoryModule;
import io.anuke.ucore.core.Core;
import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Settings;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.io.ReusableByteArrayInputStream; import io.anuke.ucore.io.ReusableByteArrayInputStream;
import io.anuke.ucore.io.delta.DEZDecoder;
import io.anuke.ucore.modules.Module; import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Pooling;
import io.anuke.ucore.util.Timer; import io.anuke.ucore.util.Timer;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Random; import java.util.Random;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
@@ -46,7 +43,7 @@ import static io.anuke.mindustry.Vars.*;
public class NetClient extends Module{ public class NetClient extends Module{
private final static float dataTimeout = 60 * 18; private final static float dataTimeout = 60 * 18;
private final static float playerSyncTime = 2; private final static float playerSyncTime = 2;
private final static IntArray removals = new IntArray(); private final static float viewScale = 1.75f;
private Timer timer = new Timer(5); private Timer timer = new Timer(5);
/**Whether the client is currently connecting.*/ /**Whether the client is currently connecting.*/
@@ -61,18 +58,13 @@ public class NetClient extends Module{
/**Last snapshot ID recieved.*/ /**Last snapshot ID recieved.*/
private int lastSnapshotBaseID = -1; private int lastSnapshotBaseID = -1;
private IntMap<byte[]> recievedSnapshots = new IntMap<>();
/**Current snapshot that is being built from chinks.*/ /**Current snapshot that is being built from chinks.*/
private byte[] currentSnapshot; private byte[] currentSnapshot;
/**Array of recieved chunk statuses.*/
private boolean[] recievedChunks;
/**Counter of how many chunks have been recieved.*/ /**Counter of how many chunks have been recieved.*/
private int recievedChunkCounter; private int recievedChunkCounter;
/**ID of snapshot that is currently being constructed.*/ /**ID of snapshot that is currently being constructed.*/
private int currentSnapshotID = -1; private int currentSnapshotID = -1;
/**Decoder for uncompressing snapshots.*/
private DEZDecoder decoder = new DEZDecoder();
/**List of entities that were removed, and need not be added while syncing.*/ /**List of entities that were removed, and need not be added while syncing.*/
private IntSet removed = new IntSet(); private IntSet removed = new IntSet();
/**Byte stream for reading in snapshots.*/ /**Byte stream for reading in snapshots.*/
@@ -198,14 +190,14 @@ public class NetClient extends Module{
} }
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){ public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength){
int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize); int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize);
if(NetServer.debugSnapshots) if(NetServer.debugSnapshots)
Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} / "+totalChunks+" totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID); Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} / "+totalChunks+" totalLength {3} bclient-base {4}", chunk.length, snapshotID, chunkID, totalLength, netClient.lastSnapshotBaseID);
//skip snapshot IDs that have already been recieved OR snapshots that are too far in front //skip snapshot IDs that have already been recieved OR snapshots that are too far in front
if(base != -1 && (snapshotID < netClient.lastSnapshotBaseID || !netClient.recievedSnapshots.containsKey(base))){ if(snapshotID < netClient.lastSnapshotBaseID){
if(NetServer.debugSnapshots) Log.info("//SKIP SNAPSHOT"); if(NetServer.debugSnapshots) Log.info("//SKIP SNAPSHOT");
return; return;
} }
@@ -218,21 +210,16 @@ public class NetClient extends Module{
//total amount of chunks to recieve //total amount of chunks to recieve
//reset status when a new snapshot sending begins //reset status when a new snapshot sending begins
if(netClient.currentSnapshotID != snapshotID || netClient.recievedChunks == null || netClient.recievedChunks.length != totalChunks){ if(netClient.currentSnapshotID != snapshotID || netClient.currentSnapshot == null){
netClient.currentSnapshotID = snapshotID; netClient.currentSnapshotID = snapshotID;
netClient.currentSnapshot = new byte[totalLength]; netClient.currentSnapshot = new byte[totalLength];
netClient.recievedChunkCounter = 0; netClient.recievedChunkCounter = 0;
netClient.recievedChunks = new boolean[totalChunks];
} }
//if this chunk hasn't been recieved yet... netClient.recievedChunkCounter++; //update recieved status
if(!netClient.recievedChunks[chunkID]){ //copy the recieved bytes into the holding array
netClient.recievedChunks[chunkID] = true; System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize,
netClient.recievedChunkCounter++; //update recieved status Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize));
//copy the recieved bytes into the holding array
System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize,
Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize));
}
//when all chunks have been recieved, begin //when all chunks have been recieved, begin
if(netClient.recievedChunkCounter >= totalChunks && netClient.currentSnapshot != null){ if(netClient.recievedChunkCounter >= totalChunks && netClient.currentSnapshot != null){
@@ -247,22 +234,8 @@ public class NetClient extends Module{
if(NetServer.debugSnapshots) if(NetServer.debugSnapshots)
Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length); Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
byte[] result; byte[] result = snapshot;
int length; int length = snapshot.length;
if(base == -1){ //fresh snapshot
result = snapshot;
length = snapshot.length;
netClient.recievedSnapshots.put(snapshotID, Arrays.copyOf(snapshot, snapshot.length));
}else{ //otherwise, last snapshot must not be null, decode it
byte[] baseBytes = netClient.recievedSnapshots.get(base);
if(NetServer.debugSnapshots)
Log.info("Base size: {0} Patch size: {1}", baseBytes.length, snapshot.length);
netClient.decoder.init(baseBytes, snapshot);
result = netClient.decoder.decode();
length = netClient.decoder.getDecodedLength();
//set last snapshot to a copy to prevent issues
netClient.recievedSnapshots.put(snapshotID, Arrays.copyOf(result, length));
}
netClient.lastSnapshotBaseID = snapshotID; netClient.lastSnapshotBaseID = snapshotID;
@@ -276,16 +249,6 @@ public class NetClient extends Module{
//confirm that snapshot has been recieved //confirm that snapshot has been recieved
netClient.lastSnapshotBaseID = snapshotID; netClient.lastSnapshotBaseID = snapshotID;
removals.clear();
for(Entry<byte[]> entry : netClient.recievedSnapshots.entries()){
if(entry.key < base){
removals.add(entry.key);
}
}
for(int i = 0; i < removals.size; i++){
netClient.recievedSnapshots.remove(removals.get(i));
}
}catch(Exception e){ }catch(Exception e){
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -389,7 +352,6 @@ public class NetClient extends Module{
connecting = true; connecting = true;
quiet = false; quiet = false;
lastSent = 0; lastSent = 0;
recievedSnapshots.clear();
currentSnapshot = null; currentSnapshot = null;
currentSnapshotID = -1; currentSnapshotID = -1;
lastSnapshotBaseID = -1; lastSnapshotBaseID = -1;
@@ -418,11 +380,19 @@ public class NetClient extends Module{
void sync(){ void sync(){
if(timer.get(0, playerSyncTime)){ if(timer.get(0, playerSyncTime)){
Player player = players[0];
BuildRequest[] requests = new BuildRequest[player.getPlaceQueue().size];
for(int i = 0; i < requests.length; i++){
requests[i] = player.getPlaceQueue().get(i);
}
ClientSnapshotPacket packet = Pooling.obtain(ClientSnapshotPacket.class); Call.onClientShapshot(lastSent++, TimeUtils.millis(), player.x, player.y,
packet.lastSnapshot = lastSnapshotBaseID; player.pointerX, player.pointerY, player.rotation, player.baseRotation,
packet.snapid = lastSent++; player.getVelocity().x, player.getVelocity().y,
Net.send(packet, SendMode.udp); player.getMineTile(),
player.isBoosting, player.isShooting, player.isAlt, requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.viewportWidth * Core.camera.zoom * viewScale, Core.camera.viewportHeight * Core.camera.zoom * viewScale);
} }
if(timer.get(1, 60)){ if(timer.get(1, 60)){

View File

@@ -2,9 +2,9 @@ package io.anuke.mindustry.core;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.graphics.Colors;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.TimeUtils; import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Loc;
@@ -26,12 +26,10 @@ import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.entities.EntityPhysics;
import io.anuke.ucore.entities.trait.Entity; import io.anuke.ucore.entities.trait.Entity;
import io.anuke.ucore.io.ByteBufferOutput; import io.anuke.ucore.io.ByteBufferOutput;
import io.anuke.ucore.io.CountableByteArrayOutputStream; import io.anuke.ucore.io.CountableByteArrayOutputStream;
import io.anuke.ucore.io.delta.ByteDeltaEncoder;
import io.anuke.ucore.io.delta.ByteMatcherHash;
import io.anuke.ucore.io.delta.DEZEncoder;
import io.anuke.ucore.modules.Module; import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Mathf;
@@ -56,7 +54,8 @@ public class NetServer extends Module{
private final static byte[] reusableSnapArray = new byte[maxSnapshotSize]; private final static byte[] reusableSnapArray = new byte[maxSnapshotSize];
private final static float serverSyncTime = 4, kickDuration = 30 * 1000; private final static float serverSyncTime = 4, kickDuration = 30 * 1000;
private final static Vector2 vector = new Vector2(); private final static Vector2 vector = new Vector2();
private final static IntArray removals = new IntArray(); private final static Rectangle viewport = new Rectangle();
private final static Array<Entity> returnArray = new Array<>();
/**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/ /**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/
private final static float correctDist = 16f; private final static float correctDist = 16f;
@@ -73,8 +72,6 @@ public class NetServer extends Module{
private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream(); private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream();
/**Data stream for writing player sync data to.*/ /**Data stream for writing player sync data to.*/
private DataOutputStream dataStream = new DataOutputStream(syncStream); private DataOutputStream dataStream = new DataOutputStream(syncStream);
/**Encoder for computing snapshot deltas.*/
private DEZEncoder encoder = new DEZEncoder();
public NetServer(){ public NetServer(){
@@ -214,87 +211,6 @@ public class NetServer extends Module{
Platform.instance.updateRPC(); Platform.instance.updateRPC();
}); });
//update last recieved snapshot based on client snapshot
Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {
Player player = connections.get(id);
NetConnection connection = Net.getConnection(id);
if(player == null || connection == null || packet.snapid < connection.lastRecievedClientSnapshot) return;
boolean verifyPosition = !player.isDead() && !debug && headless && player.getCarrier() == null;
if(connection.lastRecievedClientTime == 0) connection.lastRecievedClientTime = TimeUtils.millis() - 16;
long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedClientTime);
float maxSpeed = packet.boosting && !player.mech.flying ? player.mech.boostSpeed : player.mech.speed;
float maxMove = elapsed / 1000f * 60f * Math.min(compound(maxSpeed, player.mech.drag) * 1.2f, player.mech.maxSpeed * 1.05f);
player.pointerX = packet.pointerX;
player.pointerY = packet.pointerY;
player.setMineTile(packet.mining);
player.isBoosting = packet.boosting;
player.isShooting = packet.shooting;
player.isAlt = packet.alting;
player.getPlaceQueue().clear();
for(BuildRequest req : packet.requests){
//auto-skip done requests
if(req.remove && world.tile(req.x, req.y).block() == Blocks.air){
continue;
}else if(!req.remove && world.tile(req.x, req.y).block() == req.recipe.result && (!req.recipe.result.rotate || world.tile(req.x, req.y).getRotation() == req.rotation)){
continue;
}
player.getPlaceQueue().addLast(req);
}
vector.set(packet.x - player.getInterpolator().target.x, packet.y - player.getInterpolator().target.y);
vector.limit(maxMove);
float prevx = player.x, prevy = player.y;
player.set(player.getInterpolator().target.x, player.getInterpolator().target.y);
if(!player.mech.flying && player.boostHeat < 0.01f){
player.move(vector.x, vector.y);
}else{
player.x += vector.x;
player.y += vector.y;
}
float newx = player.x, newy = player.y;
if(!verifyPosition){
player.x = prevx;
player.y = prevy;
newx = packet.x;
newy = packet.y;
}else if(Vector2.dst(packet.x, packet.y, newx, newy) > correctDist){
Call.onPositionSet(id, newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
player.x = prevx;
player.y = prevy;
//set interpolator target to *new* position so it moves toward it
player.getInterpolator().read(player.x, player.y, newx, newy, packet.timeSent, packet.rotation, packet.baseRotation);
player.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player
//when the client confirms recieveing a snapshot, update base and clear map
if(packet.lastSnapshot > connection.lastRecievedSnapshotID){
connection.lastRecievedSnapshotID = packet.lastSnapshot;
removals.clear();
for(IntMap.Entry entry : connection.sent){
if(entry.key < packet.lastSnapshot){
removals.add(entry.key);
}
}
for(int i = 0; i < removals.size; i++){
connection.sent.remove(removals.get(i));
}
}
connection.lastRecievedClientSnapshot = packet.snapid;
connection.lastRecievedClientTime = TimeUtils.millis();
});
Net.handleServer(InvokePacket.class, (id, packet) -> { Net.handleServer(InvokePacket.class, (id, packet) -> {
Player player = connections.get(id); Player player = connections.get(id);
if(player == null) return; if(player == null) return;
@@ -302,7 +218,7 @@ public class NetServer extends Module{
}); });
} }
private float compound(float speed, float drag){ private static float compound(float speed, float drag){
float total = 0f; float total = 0f;
for(int i = 0; i < 20; i++){ for(int i = 0; i < 20; i++){
total *= (1f - drag); total *= (1f - drag);
@@ -312,9 +228,9 @@ public class NetServer extends Module{
} }
/** Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/ /** Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/
private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){ private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID){
if(bytes.length < maxSnapshotSize){ if(bytes.length < maxSnapshotSize){
scheduleSnapshot(() -> Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base)); scheduleSnapshot(() -> Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length));
}else{ }else{
int remaining = bytes.length; int remaining = bytes.length;
int offset = 0; int offset = 0;
@@ -331,7 +247,7 @@ public class NetServer extends Module{
} }
short fchunk = (short)chunkid; short fchunk = (short)chunkid;
scheduleSnapshot(() -> Call.onSnapshot(userid, toSend, snapshotID, fchunk, bytes.length, base)); scheduleSnapshot(() -> Call.onSnapshot(userid, toSend, snapshotID, fchunk, bytes.length));
remaining -= used; remaining -= used;
offset += used; offset += used;
@@ -370,6 +286,87 @@ public class NetServer extends Module{
netServer.connections.remove(player.con.id); netServer.connections.remove(player.con.id);
} }
@Remote(targets = Loc.client, unreliable = true)
public static void onClientShapshot(
Player player,
int snapshotID, long sent,
float x, float y,
float pointerX, float pointerY,
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean alting,
BuildRequest[] requests,
float viewX, float viewY, float viewWidth, float viewHeight
){
NetConnection connection = player.con;
if(connection == null || snapshotID < connection.lastRecievedClientSnapshot) return;
boolean verifyPosition = !player.isDead() && !debug && headless && player.getCarrier() == null;
if(connection.lastRecievedClientTime == 0) connection.lastRecievedClientTime = TimeUtils.millis() - 16;
connection.viewX = viewX;
connection.viewY = viewY;
connection.viewWidth = viewWidth;
connection.viewHeight = viewHeight;
long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedClientTime);
float maxSpeed = boosting && !player.mech.flying ? player.mech.boostSpeed : player.mech.speed;
float maxMove = elapsed / 1000f * 60f * Math.min(compound(maxSpeed, player.mech.drag) * 1.2f, player.mech.maxSpeed * 1.05f);
player.pointerX = pointerX;
player.pointerY = pointerY;
player.setMineTile(mining);
player.isBoosting = boosting;
player.isShooting = shooting;
player.isAlt = alting;
player.getPlaceQueue().clear();
for(BuildRequest req : requests){
//auto-skip done requests
if(req.remove && world.tile(req.x, req.y).block() == Blocks.air){
continue;
}else if(!req.remove && world.tile(req.x, req.y).block() == req.recipe.result && (!req.recipe.result.rotate || world.tile(req.x, req.y).getRotation() == req.rotation)){
continue;
}
player.getPlaceQueue().addLast(req);
}
vector.set(x - player.getInterpolator().target.x, y - player.getInterpolator().target.y);
vector.limit(maxMove);
float prevx = player.x, prevy = player.y;
player.set(player.getInterpolator().target.x, player.getInterpolator().target.y);
if(!player.mech.flying && player.boostHeat < 0.01f){
player.move(vector.x, vector.y);
}else{
player.x += vector.x;
player.y += vector.y;
}
float newx = player.x, newy = player.y;
if(!verifyPosition){
player.x = prevx;
player.y = prevy;
newx = x;
newy = y;
}else if(Vector2.dst(x, y, newx, newy) > correctDist){
Call.onPositionSet(player.con.id, newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
player.x = prevx;
player.y = prevy;
//set interpolator target to *new* position so it moves toward it
player.getInterpolator().read(player.x, player.y, newx, newy, sent, rotation, baseRotation);
player.getVelocity().set(xVelocity, yVelocity); //only for visual calculation purposes, doesn't actually update the player
connection.lastRecievedClientSnapshot = snapshotID;
connection.lastRecievedClientTime = TimeUtils.millis();
}
@Remote(targets = Loc.client, called = Loc.server) @Remote(targets = Loc.client, called = Loc.server)
public static void onAdminRequest(Player player, Player other, AdminAction action){ public static void onAdminRequest(Player player, Player other, AdminAction action){
@@ -470,6 +467,8 @@ public class NetServer extends Module{
} }
public void writeSnapshot(Player player, DataOutputStream dataStream) throws IOException{ public void writeSnapshot(Player player, DataOutputStream dataStream) throws IOException{
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);
//write wave datas //write wave datas
dataStream.writeFloat(state.wavetime); dataStream.writeFloat(state.wavetime);
dataStream.writeInt(state.wave); dataStream.writeInt(state.wave);
@@ -498,37 +497,39 @@ public class NetServer extends Module{
//check for syncable groups //check for syncable groups
for(EntityGroup<?> group : Entities.getAllGroups()){ for(EntityGroup<?> group : Entities.getAllGroups()){
//TODO screen-check sync positions to optimize?
if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue;
//clipping is done by represntatives
SyncTrait represent = (SyncTrait) group.all().get(0);
//make sure mapping is enabled for this group //make sure mapping is enabled for this group
if(!group.mappingEnabled()){ if(!group.mappingEnabled()){
throw new RuntimeException("Entity group '" + group.getType() + "' contains SyncTrait entities, yet mapping is not enabled. In order for syncing to work, you must enable mapping for this group."); throw new RuntimeException("Entity group '" + group.getType() + "' contains SyncTrait entities, yet mapping is not enabled. In order for syncing to work, you must enable mapping for this group.");
} }
int amount = 0; returnArray.clear();
if(represent.isClipped()){
for(Entity entity : group.all()){ EntityPhysics.getNearby(group, viewport, entity -> {
if(((SyncTrait) entity).isSyncing()){ if(((SyncTrait) entity).isSyncing() && viewport.contains(entity.getX(), entity.getY())){
amount++; returnArray.add(entity);
}
});
}else{
for(Entity entity : group.all()){
if(((SyncTrait) entity).isSyncing()){
returnArray.add(entity);
}
} }
} }
//write group ID + group size //write group ID + group size
dataStream.writeByte(group.getID()); dataStream.writeByte(group.getID());
dataStream.writeShort(amount); dataStream.writeShort(returnArray.size);
for(Entity entity : group.all()){ for(Entity entity : returnArray){
if(!((SyncTrait) entity).isSyncing()) continue;
int position = syncStream.position();
//write all entities now //write all entities now
dataStream.writeInt(entity.getID()); //write id dataStream.writeInt(entity.getID()); //write id
dataStream.writeByte(((SyncTrait) entity).getTypeID()); //write type ID dataStream.writeByte(((SyncTrait) entity).getTypeID()); //write type ID
((SyncTrait) entity).write(dataStream); //write entity ((SyncTrait) entity).write(dataStream); //write entity
int length = syncStream.position() - position; //length must always be less than 127 bytes
if(length > 127)
throw new RuntimeException("Write size for entity of type " + group.getType() + " must not exceed 127!");
} }
} }
} }
@@ -598,44 +599,19 @@ public class NetServer extends Module{
if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue; if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue;
//if the player hasn't acknowledged that it has recieved the packet, send the same thing again
/*if(connection.currentBaseID < connection.lastSentSnapshotID){
if(debugSnapshots)
Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length);
sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID, connection.lastSentBase);
return;
}*/
//reset stream to begin writing //reset stream to begin writing
Timers.mark();
syncStream.reset(); syncStream.reset();
writeSnapshot(player, dataStream); writeSnapshot(player, dataStream);
byte[] bytes = syncStream.toByteArray(); dataStream.close();
byte[] bytes = syncStream.toByteArray();
int snapid = connection.lastSentSnapshotID ++; int snapid = connection.lastSentSnapshotID ++;
if(connection.lastRecievedSnapshotID == -1){ if(debugSnapshots) Log.info("Sent snapshot: {0} bytes.", bytes.length);
/*if(connection.lastSentSnapshot != null){ sendSplitSnapshot(connection.id, bytes, snapid);
bytes = connection.lastSentSnapshot;
}else{
connection.lastSentRawSnapshot = bytes;
connection.lastSentSnapshot = bytes;
}*/
if(debugSnapshots) Log.info("Sent raw snapshot: {0} bytes.", bytes.length);
///Nothing to diff off of in this case, send the whole thing
sendSplitSnapshot(connection.id, bytes, snapid, -1);
connection.sent.put(snapid, bytes);
}else{
//send diff, otherwise
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.sent.get(connection.lastRecievedSnapshotID), bytes), encoder);
if(debugSnapshots)
Log.info("Shrank snapshot: {0} -> {1}, Base {2}", bytes.length, diff.length, connection.lastRecievedSnapshotID);
sendSplitSnapshot(connection.id, diff, snapid, connection.lastRecievedSnapshotID);
connection.sent.put(snapid, bytes);
}
} }
}catch(IOException e){ }catch(IOException e){

View File

@@ -25,13 +25,13 @@ public class UnitInventory implements Saveable{
@Override @Override
public void writeSave(DataOutput stream) throws IOException{ public void writeSave(DataOutput stream) throws IOException{
stream.writeShort(item.amount); stream.writeByte(item.amount);
stream.writeByte(item.item.id); stream.writeByte(item.item.id);
} }
@Override @Override
public void readSave(DataInput stream) throws IOException{ public void readSave(DataInput stream) throws IOException{
short iamount = stream.readShort(); int iamount = stream.readUnsignedByte();
byte iid = stream.readByte(); byte iid = stream.readByte();
item.item = content.item(iid); item.item = content.item(iid);

View File

@@ -12,16 +12,12 @@ import static io.anuke.mindustry.Vars.threads;
public interface SyncTrait extends Entity, TypeTrait{ public interface SyncTrait extends Entity, TypeTrait{
/** /**Whether smoothing of entities is enabled when using multithreading; not yet implemented.*/
* Whether smoothing of entities is enabled when using multithreading; not yet implemented.
*/
static boolean isSmoothing(){ static boolean isSmoothing(){
return threads.isEnabled() && threads.getTPS() <= Gdx.graphics.getFramesPerSecond() / 2f; return threads.isEnabled() && threads.getTPS() <= Gdx.graphics.getFramesPerSecond() / 2f;
} }
/** /**Sets the position of this entity and updated the interpolator.*/
* Sets the position of this entity and updated the interpolator.
*/
default void setNet(float x, float y){ default void setNet(float x, float y){
set(x, y); set(x, y);
@@ -34,9 +30,7 @@ public interface SyncTrait extends Entity, TypeTrait{
} }
} }
/** /**Interpolate entity position only. Override if you need to interpolate rotations or other values.*/
* Interpolate entity position only. Override if you need to interpolate rotations or other values.
*/
default void interpolate(){ default void interpolate(){
if(getInterpolator() == null) if(getInterpolator() == null)
throw new RuntimeException("This entity must have an interpolator to interpolate()!"); throw new RuntimeException("This entity must have an interpolator to interpolate()!");
@@ -47,20 +41,21 @@ public interface SyncTrait extends Entity, TypeTrait{
setY(getInterpolator().pos.y); setY(getInterpolator().pos.y);
} }
/** /**Return the interpolator used for smoothing the position. Optional.*/
* Return the interpolator used for smoothing the position. Optional.
*/
default Interpolator getInterpolator(){ default Interpolator getInterpolator(){
return null; return null;
} }
/** /**Whether syncing is enabled for this entity; true by default.*/
* Whether syncing is enabled for this entity; true by default.
*/
default boolean isSyncing(){ default boolean isSyncing(){
return true; return true;
} }
/**Whether this entity is clipped and not synced when out of viewport.*/
default boolean isClipped(){
return true;
}
//Read and write sync data, usually position //Read and write sync data, usually position
void write(DataOutput data) throws IOException; void write(DataOutput data) throws IOException;

View File

@@ -394,7 +394,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
public void write(DataOutput data) throws IOException{ public void write(DataOutput data) throws IOException{
super.writeSave(data); super.writeSave(data);
data.writeByte(type.id); data.writeByte(type.id);
data.writeInt(spawner);
} }
@Override @Override
@@ -402,7 +401,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
float lastx = x, lasty = y, lastrot = rotation; float lastx = x, lasty = y, lastrot = rotation;
super.readSave(data); super.readSave(data);
this.type = content.getByID(ContentType.unit, data.readByte()); this.type = content.getByID(ContentType.unit, data.readByte());
this.spawner = data.readInt();
interpolator.read(lastx, lasty, x, y, time, rotation); interpolator.read(lastx, lasty, x, y, time, rotation);
rotation = lastrot; rotation = lastrot;

View File

@@ -8,6 +8,7 @@ import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.bullet.Bullet; import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.bullet.BulletType; import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.CarriableTrait; import io.anuke.mindustry.entities.traits.CarriableTrait;
import io.anuke.mindustry.entities.traits.CarryTrait; import io.anuke.mindustry.entities.traits.CarryTrait;
import io.anuke.mindustry.entities.traits.ShooterTrait; import io.anuke.mindustry.entities.traits.ShooterTrait;
@@ -139,12 +140,13 @@ public class TypeIO{
@WriteClass(Tile.class) @WriteClass(Tile.class)
public static void writeTile(ByteBuffer buffer, Tile tile){ public static void writeTile(ByteBuffer buffer, Tile tile){
buffer.putInt(tile.packedPosition()); buffer.putInt(tile == null ? -1 : tile.packedPosition());
} }
@ReadClass(Tile.class) @ReadClass(Tile.class)
public static Tile readTile(ByteBuffer buffer){ public static Tile readTile(ByteBuffer buffer){
return world.tile(buffer.getInt()); int position = buffer.getInt();
return position == -1 ? null : world.tile(position);
} }
@WriteClass(Block.class) @WriteClass(Block.class)
@@ -157,6 +159,42 @@ public class TypeIO{
return content.block(buffer.get()); return content.block(buffer.get());
} }
@WriteClass(BuildRequest[].class)
public static void writeRequests(ByteBuffer buffer, BuildRequest[] requests){
buffer.putShort((short)requests.length);
for(BuildRequest request : requests){
buffer.put(request.remove ? (byte) 1 : 0);
buffer.putInt(world.toPacked(request.x, request.y));
if(!request.remove){
buffer.put(request.recipe.id);
buffer.put((byte) request.rotation);
}
}
}
@ReadClass(BuildRequest[].class)
public static BuildRequest[] readRequests(ByteBuffer buffer){
short reqamount = buffer.getShort();
BuildRequest[] reqs = new BuildRequest[reqamount];
for(int i = 0; i < reqamount; i++){
byte type = buffer.get();
int position = buffer.getInt();
BuildRequest currentRequest;
if(type == 1){ //remove
currentRequest = new BuildRequest(position % world.width(), position / world.width());
}else{ //place
byte recipe = buffer.get();
byte rotation = buffer.get();
currentRequest = new BuildRequest(position % world.width(), position / world.width(), rotation, content.recipe(recipe));
}
reqs[i] = (currentRequest);
}
return reqs;
}
@WriteClass(KickReason.class) @WriteClass(KickReason.class)
public static void writeKick(ByteBuffer buffer, KickReason reason){ public static void writeKick(ByteBuffer buffer, KickReason reason){
buffer.put((byte) reason.ordinal()); buffer.put((byte) reason.ordinal());

View File

@@ -1,28 +1,12 @@
package io.anuke.mindustry.net; package io.anuke.mindustry.net;
import com.badlogic.gdx.utils.IntMap;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
public abstract class NetConnection{ public abstract class NetConnection{
public final int id; public final int id;
public final String address; public final String address;
/**
* The current base snapshot that the client is absolutely confirmed to have recieved.
* All sent snapshots should be taking the diff from this base snapshot, if it isn't null.
*/
//public byte[] currentBaseSnapshot;
/**
* ID of the current base snapshot.
*/
// public int currentBaseID = -1;
//public int lastSentBase = -1;
// public byte[] lastSentSnapshot;
//public byte[] lastSentRawSnapshot;
public int lastRecievedSnapshotID = -1;
public int lastSentSnapshotID = -1; public int lastSentSnapshotID = -1;
public IntMap<byte[]> sent = new IntMap<>();
/**ID of last recieved client snapshot.*/ /**ID of last recieved client snapshot.*/
public int lastRecievedClientSnapshot = -1; public int lastRecievedClientSnapshot = -1;
@@ -31,6 +15,7 @@ public abstract class NetConnection{
public boolean hasConnected = false; public boolean hasConnected = false;
public boolean hasBegunConnecting = false; public boolean hasBegunConnecting = false;
public float viewWidth, viewHeight, viewX, viewY;
public NetConnection(int id, String address){ public NetConnection(int id, String address){
this.id = id; this.id = id;

View File

@@ -135,6 +135,10 @@ public class NetworkIO{
} }
//now write a snapshot. //now write a snapshot.
player.con.viewX = world.width() * tilesize/2f;
player.con.viewY = world.height() * tilesize/2f;
player.con.viewWidth = world.width() * tilesize;
player.con.viewHeight = world.height() * tilesize;
netServer.writeSnapshot(player, stream); netServer.writeSnapshot(player, stream);
}catch(IOException e){ }catch(IOException e){

View File

@@ -1,23 +1,12 @@
package io.anuke.mindustry.net; package io.anuke.mindustry.net;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Base64Coder; import com.badlogic.gdx.utils.Base64Coder;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.game.Version; import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.io.IOUtils; import io.anuke.ucore.io.IOUtils;
import io.anuke.ucore.util.Bundles; import io.anuke.ucore.util.Bundles;
import io.anuke.ucore.util.Mathf;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.content;
import static io.anuke.mindustry.Vars.world;
/** /**
* Class for storing all packets. * Class for storing all packets.
*/ */
@@ -147,94 +136,7 @@ public class Packets{
} }
} }
public static class ClientSnapshotPacket implements Packet{ /**Marks the beginning of a stream.*/
//snapshot meta
public int lastSnapshot;
public int snapid;
public long timeSent;
//player snapshot data
public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv;
public Tile mining;
public boolean boosting, shooting, alting;
public Array<BuildRequest> requests = new Array<>();
@Override
public void write(ByteBuffer buffer){
Player player = Vars.players[0];
buffer.putInt(lastSnapshot);
buffer.putInt(snapid);
buffer.putLong(TimeUtils.millis());
buffer.putFloat(player.x);
buffer.putFloat(player.y);
buffer.putFloat(player.pointerX);
buffer.putFloat(player.pointerY);
buffer.put(player.isBoosting ? (byte) 1 : 0);
buffer.put(player.isShooting ? (byte) 1 : 0);
buffer.put(player.isAlt ? (byte) 1 : 0);
buffer.put((byte) (Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
buffer.put((byte) (Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
//saving 4 bytes, yay?
buffer.putShort((short) (player.rotation * 2));
buffer.putShort((short) (player.baseRotation * 2));
buffer.putInt(player.getMineTile() == null ? -1 : player.getMineTile().packedPosition());
buffer.putShort((short)player.getPlaceQueue().size);
for(BuildRequest request : player.getPlaceQueue()){
buffer.put(request.remove ? (byte) 1 : 0);
buffer.putInt(world.toPacked(request.x, request.y));
if(!request.remove){
buffer.put(request.recipe.id);
buffer.put((byte) request.rotation);
}
}
}
@Override
public void read(ByteBuffer buffer){
lastSnapshot = buffer.getInt();
snapid = buffer.getInt();
timeSent = buffer.getLong();
x = buffer.getFloat();
y = buffer.getFloat();
pointerX = buffer.getFloat();
pointerY = buffer.getFloat();
boosting = buffer.get() == 1;
shooting = buffer.get() == 1;
alting = buffer.get() == 1;
xv = buffer.get() / Unit.velocityPercision;
yv = buffer.get() / Unit.velocityPercision;
rotation = buffer.getShort() / 2f;
baseRotation = buffer.getShort() / 2f;
mining = world.tile(buffer.getInt());
requests.clear();
short reqamount = buffer.getShort();
for(int i = 0; i < reqamount; i++){
byte type = buffer.get();
int position = buffer.getInt();
BuildRequest currentRequest;
if(type == 1){ //remove
currentRequest = new BuildRequest(position % world.width(), position / world.width());
}else{ //place
byte recipe = buffer.get();
byte rotation = buffer.get();
currentRequest = new BuildRequest(position % world.width(), position / world.width(), rotation, content.recipe(recipe));
}
requests.add(currentRequest);
}
}
}
/**
* Marks the beginning of a stream.
*/
public static class StreamBegin implements Packet{ public static class StreamBegin implements Packet{
private static int lastid; private static int lastid;

View File

@@ -11,7 +11,6 @@ public class Registrator{
new ClassEntry(StreamChunk.class, StreamChunk::new), new ClassEntry(StreamChunk.class, StreamChunk::new),
new ClassEntry(WorldStream.class, WorldStream::new), new ClassEntry(WorldStream.class, WorldStream::new),
new ClassEntry(ConnectPacket.class, ConnectPacket::new), new ClassEntry(ConnectPacket.class, ConnectPacket::new),
new ClassEntry(ClientSnapshotPacket.class, ClientSnapshotPacket::new),
new ClassEntry(InvokePacket.class, InvokePacket::new) new ClassEntry(InvokePacket.class, InvokePacket::new)
}; };
private static ObjectIntMap<Class> ids = new ObjectIntMap<>(); private static ObjectIntMap<Class> ids = new ObjectIntMap<>();