Removed delta compression / Removed client snapshot packet
This commit is contained in:
@@ -3,16 +3,15 @@ package io.anuke.mindustry.core;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
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.TimeUtils;
|
||||
import io.anuke.annotations.Annotations.PacketPriority;
|
||||
import io.anuke.annotations.Annotations.Remote;
|
||||
import io.anuke.annotations.Annotations.Variant;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
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.TypeTrait;
|
||||
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.TraceInfo;
|
||||
import io.anuke.mindustry.world.modules.InventoryModule;
|
||||
import io.anuke.ucore.core.Core;
|
||||
import io.anuke.ucore.core.Settings;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
import io.anuke.ucore.entities.Entities;
|
||||
import io.anuke.ucore.entities.EntityGroup;
|
||||
import io.anuke.ucore.io.ReusableByteArrayInputStream;
|
||||
import io.anuke.ucore.io.delta.DEZDecoder;
|
||||
import io.anuke.ucore.modules.Module;
|
||||
import io.anuke.ucore.util.Log;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
import io.anuke.ucore.util.Pooling;
|
||||
import io.anuke.ucore.util.Timer;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
@@ -46,7 +43,7 @@ import static io.anuke.mindustry.Vars.*;
|
||||
public class NetClient extends Module{
|
||||
private final static float dataTimeout = 60 * 18;
|
||||
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);
|
||||
/**Whether the client is currently connecting.*/
|
||||
@@ -61,18 +58,13 @@ public class NetClient extends Module{
|
||||
/**Last snapshot ID recieved.*/
|
||||
private int lastSnapshotBaseID = -1;
|
||||
|
||||
private IntMap<byte[]> recievedSnapshots = new IntMap<>();
|
||||
/**Current snapshot that is being built from chinks.*/
|
||||
private byte[] currentSnapshot;
|
||||
/**Array of recieved chunk statuses.*/
|
||||
private boolean[] recievedChunks;
|
||||
/**Counter of how many chunks have been recieved.*/
|
||||
private int recievedChunkCounter;
|
||||
/**ID of snapshot that is currently being constructed.*/
|
||||
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.*/
|
||||
private IntSet removed = new IntSet();
|
||||
/**Byte stream for reading in snapshots.*/
|
||||
@@ -198,14 +190,14 @@ public class NetClient extends Module{
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
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
|
||||
if(base != -1 && (snapshotID < netClient.lastSnapshotBaseID || !netClient.recievedSnapshots.containsKey(base))){
|
||||
if(snapshotID < netClient.lastSnapshotBaseID){
|
||||
if(NetServer.debugSnapshots) Log.info("//SKIP SNAPSHOT");
|
||||
return;
|
||||
}
|
||||
@@ -218,21 +210,16 @@ public class NetClient extends Module{
|
||||
//total amount of chunks to recieve
|
||||
|
||||
//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.currentSnapshot = new byte[totalLength];
|
||||
netClient.recievedChunkCounter = 0;
|
||||
netClient.recievedChunks = new boolean[totalChunks];
|
||||
}
|
||||
|
||||
//if this chunk hasn't been recieved yet...
|
||||
if(!netClient.recievedChunks[chunkID]){
|
||||
netClient.recievedChunks[chunkID] = true;
|
||||
netClient.recievedChunkCounter++; //update recieved status
|
||||
//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));
|
||||
}
|
||||
netClient.recievedChunkCounter++; //update recieved status
|
||||
//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
|
||||
if(netClient.recievedChunkCounter >= totalChunks && netClient.currentSnapshot != null){
|
||||
@@ -247,22 +234,8 @@ public class NetClient extends Module{
|
||||
if(NetServer.debugSnapshots)
|
||||
Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
|
||||
|
||||
byte[] result;
|
||||
int 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));
|
||||
}
|
||||
byte[] result = snapshot;
|
||||
int length = snapshot.length;
|
||||
|
||||
netClient.lastSnapshotBaseID = snapshotID;
|
||||
|
||||
@@ -276,16 +249,6 @@ public class NetClient extends Module{
|
||||
|
||||
//confirm that snapshot has been recieved
|
||||
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){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -389,7 +352,6 @@ public class NetClient extends Module{
|
||||
connecting = true;
|
||||
quiet = false;
|
||||
lastSent = 0;
|
||||
recievedSnapshots.clear();
|
||||
currentSnapshot = null;
|
||||
currentSnapshotID = -1;
|
||||
lastSnapshotBaseID = -1;
|
||||
@@ -418,11 +380,19 @@ public class NetClient extends Module{
|
||||
void sync(){
|
||||
|
||||
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);
|
||||
packet.lastSnapshot = lastSnapshotBaseID;
|
||||
packet.snapid = lastSent++;
|
||||
Net.send(packet, SendMode.udp);
|
||||
Call.onClientShapshot(lastSent++, TimeUtils.millis(), player.x, player.y,
|
||||
player.pointerX, player.pointerY, player.rotation, player.baseRotation,
|
||||
player.getVelocity().x, player.getVelocity().y,
|
||||
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)){
|
||||
|
||||
@@ -2,9 +2,9 @@ package io.anuke.mindustry.core;
|
||||
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.graphics.Colors;
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.IntArray;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
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.entities.Entities;
|
||||
import io.anuke.ucore.entities.EntityGroup;
|
||||
import io.anuke.ucore.entities.EntityPhysics;
|
||||
import io.anuke.ucore.entities.trait.Entity;
|
||||
import io.anuke.ucore.io.ByteBufferOutput;
|
||||
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.util.Log;
|
||||
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 float serverSyncTime = 4, kickDuration = 30 * 1000;
|
||||
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.*/
|
||||
private final static float correctDist = 16f;
|
||||
|
||||
@@ -73,8 +72,6 @@ public class NetServer extends Module{
|
||||
private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream();
|
||||
/**Data stream for writing player sync data to.*/
|
||||
private DataOutputStream dataStream = new DataOutputStream(syncStream);
|
||||
/**Encoder for computing snapshot deltas.*/
|
||||
private DEZEncoder encoder = new DEZEncoder();
|
||||
|
||||
public NetServer(){
|
||||
|
||||
@@ -214,87 +211,6 @@ public class NetServer extends Module{
|
||||
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) -> {
|
||||
Player player = connections.get(id);
|
||||
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;
|
||||
for(int i = 0; i < 20; i++){
|
||||
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.*/
|
||||
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){
|
||||
scheduleSnapshot(() -> Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base));
|
||||
scheduleSnapshot(() -> Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length));
|
||||
}else{
|
||||
int remaining = bytes.length;
|
||||
int offset = 0;
|
||||
@@ -331,7 +247,7 @@ public class NetServer extends Module{
|
||||
}
|
||||
|
||||
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;
|
||||
offset += used;
|
||||
@@ -370,6 +286,87 @@ public class NetServer extends Module{
|
||||
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)
|
||||
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{
|
||||
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);
|
||||
|
||||
//write wave datas
|
||||
dataStream.writeFloat(state.wavetime);
|
||||
dataStream.writeInt(state.wave);
|
||||
@@ -498,37 +497,39 @@ public class NetServer extends Module{
|
||||
|
||||
//check for syncable groups
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
//TODO screen-check sync positions to optimize?
|
||||
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
|
||||
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.");
|
||||
}
|
||||
|
||||
int amount = 0;
|
||||
|
||||
for(Entity entity : group.all()){
|
||||
if(((SyncTrait) entity).isSyncing()){
|
||||
amount++;
|
||||
returnArray.clear();
|
||||
if(represent.isClipped()){
|
||||
EntityPhysics.getNearby(group, viewport, entity -> {
|
||||
if(((SyncTrait) entity).isSyncing() && viewport.contains(entity.getX(), entity.getY())){
|
||||
returnArray.add(entity);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
for(Entity entity : group.all()){
|
||||
if(((SyncTrait) entity).isSyncing()){
|
||||
returnArray.add(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//write group ID + group size
|
||||
dataStream.writeByte(group.getID());
|
||||
dataStream.writeShort(amount);
|
||||
dataStream.writeShort(returnArray.size);
|
||||
|
||||
for(Entity entity : group.all()){
|
||||
if(!((SyncTrait) entity).isSyncing()) continue;
|
||||
|
||||
int position = syncStream.position();
|
||||
for(Entity entity : returnArray){
|
||||
//write all entities now
|
||||
dataStream.writeInt(entity.getID()); //write id
|
||||
dataStream.writeByte(((SyncTrait) entity).getTypeID()); //write type ID
|
||||
((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 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
|
||||
Timers.mark();
|
||||
syncStream.reset();
|
||||
|
||||
writeSnapshot(player, dataStream);
|
||||
|
||||
byte[] bytes = syncStream.toByteArray();
|
||||
dataStream.close();
|
||||
|
||||
byte[] bytes = syncStream.toByteArray();
|
||||
int snapid = connection.lastSentSnapshotID ++;
|
||||
|
||||
if(connection.lastRecievedSnapshotID == -1){
|
||||
/*if(connection.lastSentSnapshot != null){
|
||||
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);
|
||||
}
|
||||
if(debugSnapshots) Log.info("Sent snapshot: {0} bytes.", bytes.length);
|
||||
sendSplitSnapshot(connection.id, bytes, snapid);
|
||||
}
|
||||
|
||||
}catch(IOException e){
|
||||
|
||||
@@ -25,13 +25,13 @@ public class UnitInventory implements Saveable{
|
||||
|
||||
@Override
|
||||
public void writeSave(DataOutput stream) throws IOException{
|
||||
stream.writeShort(item.amount);
|
||||
stream.writeByte(item.amount);
|
||||
stream.writeByte(item.item.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
short iamount = stream.readShort();
|
||||
int iamount = stream.readUnsignedByte();
|
||||
byte iid = stream.readByte();
|
||||
|
||||
item.item = content.item(iid);
|
||||
|
||||
@@ -12,16 +12,12 @@ import static io.anuke.mindustry.Vars.threads;
|
||||
|
||||
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(){
|
||||
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){
|
||||
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(){
|
||||
if(getInterpolator() == null)
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interpolator used for smoothing the position. Optional.
|
||||
*/
|
||||
/**Return the interpolator used for smoothing the position. Optional.*/
|
||||
default Interpolator getInterpolator(){
|
||||
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(){
|
||||
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
|
||||
void write(DataOutput data) throws IOException;
|
||||
|
||||
|
||||
@@ -394,7 +394,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
public void write(DataOutput data) throws IOException{
|
||||
super.writeSave(data);
|
||||
data.writeByte(type.id);
|
||||
data.writeInt(spawner);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -402,7 +401,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
float lastx = x, lasty = y, lastrot = rotation;
|
||||
super.readSave(data);
|
||||
this.type = content.getByID(ContentType.unit, data.readByte());
|
||||
this.spawner = data.readInt();
|
||||
|
||||
interpolator.read(lastx, lasty, x, y, time, rotation);
|
||||
rotation = lastrot;
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.anuke.mindustry.entities.Player;
|
||||
import io.anuke.mindustry.entities.Unit;
|
||||
import io.anuke.mindustry.entities.bullet.Bullet;
|
||||
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.CarryTrait;
|
||||
import io.anuke.mindustry.entities.traits.ShooterTrait;
|
||||
@@ -139,12 +140,13 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Tile.class)
|
||||
public static void writeTile(ByteBuffer buffer, Tile tile){
|
||||
buffer.putInt(tile.packedPosition());
|
||||
buffer.putInt(tile == null ? -1 : tile.packedPosition());
|
||||
}
|
||||
|
||||
@ReadClass(Tile.class)
|
||||
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)
|
||||
@@ -157,6 +159,42 @@ public class TypeIO{
|
||||
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)
|
||||
public static void writeKick(ByteBuffer buffer, KickReason reason){
|
||||
buffer.put((byte) reason.ordinal());
|
||||
|
||||
@@ -1,28 +1,12 @@
|
||||
package io.anuke.mindustry.net;
|
||||
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import io.anuke.mindustry.net.Net.SendMode;
|
||||
|
||||
public abstract class NetConnection{
|
||||
public final int id;
|
||||
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 IntMap<byte[]> sent = new IntMap<>();
|
||||
|
||||
/**ID of last recieved client snapshot.*/
|
||||
public int lastRecievedClientSnapshot = -1;
|
||||
@@ -31,6 +15,7 @@ public abstract class NetConnection{
|
||||
|
||||
public boolean hasConnected = false;
|
||||
public boolean hasBegunConnecting = false;
|
||||
public float viewWidth, viewHeight, viewX, viewY;
|
||||
|
||||
public NetConnection(int id, String address){
|
||||
this.id = id;
|
||||
|
||||
@@ -135,6 +135,10 @@ public class NetworkIO{
|
||||
}
|
||||
|
||||
//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);
|
||||
|
||||
}catch(IOException e){
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
package io.anuke.mindustry.net;
|
||||
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
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.world.Tile;
|
||||
import io.anuke.ucore.io.IOUtils;
|
||||
import io.anuke.ucore.util.Bundles;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
/**
|
||||
* Class for storing all packets.
|
||||
*/
|
||||
@@ -147,94 +136,7 @@ public class Packets{
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientSnapshotPacket implements Packet{
|
||||
//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.
|
||||
*/
|
||||
/**Marks the beginning of a stream.*/
|
||||
public static class StreamBegin implements Packet{
|
||||
private static int lastid;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ public class Registrator{
|
||||
new ClassEntry(StreamChunk.class, StreamChunk::new),
|
||||
new ClassEntry(WorldStream.class, WorldStream::new),
|
||||
new ClassEntry(ConnectPacket.class, ConnectPacket::new),
|
||||
new ClassEntry(ClientSnapshotPacket.class, ClientSnapshotPacket::new),
|
||||
new ClassEntry(InvokePacket.class, InvokePacket::new)
|
||||
};
|
||||
private static ObjectIntMap<Class> ids = new ObjectIntMap<>();
|
||||
|
||||
Reference in New Issue
Block a user