1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-22 05:47:44 +03:00

Implemented scalable snapshot sizes

This commit is contained in:
Anuken 2018-06-28 20:49:12 -04:00
parent fdf7173f14
commit ab82faf7ba
9 changed files with 124 additions and 11 deletions

View File

@ -437,3 +437,6 @@ block.itemvoid.name=Item Void
block.liquidsource.name=Liquid Source
block.powervoid.name=Power Void
block.powerinfinite.name=Power Infinite
block.unloader.name=Unloader
block.sortedunloader.name=Sorted Unloader
block.vault.name=Vault

View File

@ -12,9 +12,11 @@ import io.anuke.mindustry.entities.effect.Fire;
import io.anuke.mindustry.entities.effect.ItemDrop;
import io.anuke.mindustry.entities.effect.Puddle;
import io.anuke.mindustry.entities.effect.Shield;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.units.BaseUnit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Net;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.entities.trait.DrawTrait;
@ -158,12 +160,20 @@ public class Vars{
fireGroup = Entities.addGroup(Fire.class, false).enableMapping();
unitGroups = new EntityGroup[Team.all.length];
threads = new ThreadHandler(Platform.instance.getThreadProvider());
for(Team team : Team.all){
unitGroups[team.ordinal()] = Entities.addGroup(BaseUnit.class).enableMapping();
}
for(EntityGroup<?> group : Entities.getAllGroups()){
group.setRemoveListener(entity -> {
if(entity instanceof SyncTrait && Net.client()){
netClient.addRemovedEntity(((SyncTrait) entity).getID());
}
});
}
threads = new ThreadHandler(Platform.instance.getThreadProvider());
mobile = Gdx.app.getType() == ApplicationType.Android || Gdx.app.getType() == ApplicationType.iOS || testMobile;
ios = Gdx.app.getType() == ApplicationType.iOS;
android = Gdx.app.getType() == ApplicationType.Android;

View File

@ -2,6 +2,7 @@ package io.anuke.mindustry.core;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Base64Coder;
import com.badlogic.gdx.utils.IntSet;
import com.badlogic.gdx.utils.Pools;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.annotations.Annotations.Variant;
@ -24,6 +25,7 @@ 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.Timer;
import java.io.DataInputStream;
@ -47,10 +49,20 @@ public class NetClient extends Module {
private int lastSent;
/**Last snapshot recieved.*/
private byte[] lastSnapshot;
/**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;
/**Last snapshot ID recieved.*/
private int lastSnapshotID = -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.*/
private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream();
private DataInputStream dataStream = new DataInputStream(byteStream);
@ -63,9 +75,15 @@ public class NetClient extends Module {
player.isAdmin = false;
Net.setClientLoaded(false);
removed.clear();
timeoutTime = 0f;
connecting = true;
quiet = false;
lastSent = 0;
lastSnapshot = null;
currentSnapshot = null;
currentSnapshotID = 0;
lastSnapshotID = -1;
ui.chatfrag.clearMessages();
ui.loadfrag.hide();
@ -160,6 +178,14 @@ public class NetClient extends Module {
Net.disconnect();
}
public synchronized void addRemovedEntity(int id){
removed.add(id);
}
public synchronized boolean isEntityUsed(int id){
return removed.contains(id);
}
void sync(){
if(timer.get(0, playerSyncTime)){
@ -213,13 +239,47 @@ public class NetClient extends Module {
}
@Remote(variants = Variant.one, unreliable = true)
public static void onSnapshot(byte[] snapshot, int snapshotID){
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, short totalLength){
//skip snapshot IDs that have already been recieved
if(snapshotID == netClient.lastSnapshotID){
return;
}
try {
byte[] snapshot;
//total length exceeds that needed to hold one snapshot, therefore, it is split into chunks
if(totalLength > NetServer.maxSnapshotSize) {
//total amount of chunks to recieve
int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize);
//reset status when a new snapshot sending begins
if (netClient.currentSnapshotID != snapshotID) {
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));
}
//when all chunks have been recieved, begin
if(netClient.recievedChunkCounter >= totalChunks){
snapshot = netClient.currentSnapshot;
}else{
return;
}
}else{
snapshot = chunk;
}
byte[] result;
int length;
if (snapshotID == 0) { //fresh snapshot
@ -236,7 +296,7 @@ public class NetClient extends Module {
netClient.lastSnapshotID = snapshotID;
//set stream bytes to begin write
//set stream bytes to begin snapshot reaeding
netClient.byteStream.setBytes(result, 0, length);
//get data input for reading from the stream
@ -287,8 +347,9 @@ public class NetClient extends Module {
throw new RuntimeException("Error reading entity of type '"+ group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1)+ "]");
}
if(add){
if(add && !netClient.isEntityUsed(entity.getID())){
entity.add();
netClient.addRemovedEntity(entity.getID());
}
}
}

View File

@ -34,10 +34,14 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import static io.anuke.mindustry.Vars.*;
public class NetServer extends Module{
public final static int maxSnapshotSize = 2047;
private final static boolean showSnapshotSize = false;
private final static float serverSyncTime = 4, kickDuration = 30 * 1000;
private final static Vector2 vector = new Vector2();
/**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/
@ -319,7 +323,7 @@ public class NetServer extends Module{
//if the player hasn't acknowledged that it has recieved the packet, send the same thing again
if(connection.lastSentSnapshotID > connection.lastSnapshotID){
Call.onSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID);
sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID);
return;
}else{
//set up last confirmed snapshot to the last one that was sent, otherwise
@ -394,13 +398,17 @@ public class NetServer extends Module{
byte[] bytes = syncStream.toByteArray();
connection.lastSentSnapshot = bytes;
if(connection.lastSnapshotID == -1){
if(showSnapshotSize) Log.info("Sent raw snapshot: {0} bytes.", bytes.length);
//no snapshot to diff, send it all
Call.onSnapshot(connection.id, bytes, 0);
//Call.onSnapshot(connection.id, bytes, 0, 0);
sendSplitSnapshot(connection.id, bytes, 0);
connection.lastSnapshotID = 0;
}else{
//send diff, otherwise
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder);
Call.onSnapshot(connection.id, diff, connection.lastSnapshotID + 1);
if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}", bytes.length, diff.length);
//Call.onSnapshot(connection.id, diff, connection.lastSnapshotID + 1, 0);
sendSplitSnapshot(connection.id, diff, connection.lastSnapshotID + 1);
//increment snapshot ID
connection.lastSentSnapshotID ++;
}
@ -411,6 +419,27 @@ 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){
if(bytes.length < maxSnapshotSize){
Call.onSnapshot(userid, bytes, snapshotID, (short)0, (short)bytes.length);
}else{
int remaining = bytes.length;
int offset = 0;
int chunkid = 0;
while(remaining > 0){
int used = Math.min(remaining, maxSnapshotSize);
//TODO optimize to *not* copy the bytes directly, but instead re-use all arrays that are of length = maxSnapshotSize
byte[] toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length));
Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, (short)bytes.length);
remaining -= used;
offset += used;
chunkid ++;
}
}
}
private static void onDisconnect(Player player){
Call.sendMessage("[accent]" + player.name + " has disconnected.");
Call.onPlayerDisconnect(player.id);

View File

@ -1,14 +1,20 @@
package io.anuke.mindustry.entities.units;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.gen.CallEntity;
import io.anuke.mindustry.type.Item;
import io.anuke.ucore.util.Mathf;
public class UnitDrops {
private static final int maxItems = 200;
private static Item[] dropTable;
public static void dropItems(BaseUnit unit){
if(Vars.itemGroup.size() > maxItems){
return;
}
if(dropTable == null){
dropTable = new Item[]{Items.tungsten, Items.lead, Items.carbide};
}

View File

@ -8,6 +8,7 @@ import io.anuke.mindustry.game.UnlockableContent;
import io.anuke.mindustry.world.Block;
import io.anuke.ucore.util.Bundles;
import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Strings;
import java.util.Arrays;
@ -59,7 +60,8 @@ public class Recipe implements UnlockableContent{
@Override
public void init() {
if(!Bundles.has("block." + result.name + ".name")) {
Log.err("WARNING: Recipe block '{0}' does not have a formal name defined.", result.name);
Log.err("WARNING: Recipe block '{0}' does not have a formal name defined. Add the following to bundle.properties:", result.name);
Log.err("block.{0}.name={1}", result.name, Strings.capitalize(result.name.replace('-', '_')));
}/*else if(result.fullDescription == null){
Log.err("WARNING: Recipe block '{0}' does not have a description defined.", result.name);
}*/

View File

@ -132,6 +132,7 @@ public class Weapon extends Upgrade {
@Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true)
public static void onPlayerShootWeapon(Player player, float x, float y, float rotation, boolean left){
if(player == null) return;
//clients do not see their own shoot events: they are simulated completely clientside to prevent laggy visuals
//messing with the firerate or any other stats does not affect the server (take that, script kiddies!)
if(Net.client() && player == Vars.players[0]){
@ -143,6 +144,7 @@ public class Weapon extends Upgrade {
@Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true)
public static void onGenericShootWeapon(ShooterTrait shooter, float x, float y, float rotation, boolean left){
if(shooter == null) return;
shootDirect(shooter, x, y, rotation, left);
}

View File

@ -54,7 +54,7 @@ public class KryoClient implements ClientProvider{
}
};
client = new Client(8192, 2048, connection -> new ByteSerializer());
client = new Client(8192, 4096, connection -> new ByteSerializer());
client.setDiscoveryHandler(handler);
Listener listener = new Listener(){

View File

@ -52,7 +52,7 @@ public class KryoServer implements ServerProvider {
int lastconnection = 0;
public KryoServer(){
server = new Server(4096*2, 2048, connection -> new ByteSerializer());
server = new Server(4096*2, 4096, connection -> new ByteSerializer());
server.setDiscoveryHandler((datagramChannel, fromAddress) -> {
ByteBuffer buffer = NetworkIO.writeServerData();
buffer.position(0);