From d4d9d59fe43bb5533bad8667e0e96c6f19e53466 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 15 Aug 2018 17:25:19 -0400 Subject: [PATCH] Server sector commands / World data compression / Snapshot sent w/ world --- .../io/anuke/mindustry/core/NetClient.java | 116 +++++++-------- .../io/anuke/mindustry/core/NetServer.java | 132 +++++++++--------- core/src/io/anuke/mindustry/core/World.java | 3 - .../src/io/anuke/mindustry/net/NetworkIO.java | 6 + .../anuke/mindustry/server/ServerControl.java | 40 +++++- 5 files changed, 169 insertions(+), 128 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 999f9452ee..ae883975c0 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -31,8 +31,10 @@ 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; import static io.anuke.mindustry.Vars.*; @@ -134,7 +136,7 @@ public class NetClient extends Module{ Net.handleClient(WorldStream.class, data -> { Log.info("Recieved world data: {0} bytes.", data.stream.available()); - NetworkIO.loadWorld(data.stream); + NetworkIO.loadWorld(new InflaterInputStream(data.stream)); finishConnecting(); }); @@ -249,68 +251,72 @@ public class NetClient extends Module{ //get data input for reading from the stream DataInputStream input = netClient.dataStream; - //read wave info - state.wavetime = input.readFloat(); - state.wave = input.readInt(); - - byte cores = input.readByte(); - for(int i = 0; i < cores; i++){ - int pos = input.readInt(); - world.tile(pos).entity.items.read(input); - } - - long timestamp = input.readLong(); - - byte totalGroups = input.readByte(); - //for each group... - for(int i = 0; i < totalGroups; i++){ - //read group info - byte groupID = input.readByte(); - short amount = input.readShort(); - - EntityGroup group = Entities.getGroup(groupID); - - //go through each entity - for(int j = 0; j < amount; j++){ - int position = netClient.byteStream.position(); //save position to check read/write correctness - int id = input.readInt(); - byte typeID = input.readByte(); - - SyncTrait entity = (SyncTrait) group.getByID(id); - boolean add = false; - - //entity must not be added yet, so create it - if(entity == null){ - entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier - entity.resetID(id); - if(!netClient.isEntityUsed(entity.getID())){ - add = true; - } - } - - //read the entity - entity.read(input, timestamp); - - byte readLength = input.readByte(); - if(netClient.byteStream.position() - position - 1 != readLength){ - throw new RuntimeException("Error reading entity of type '" + group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1) + "]"); - } - - if(add){ - entity.add(); - netClient.addRemovedEntity(entity.getID()); - } - } - } + netClient.readSnapshot(input); //confirm that snapshot has been recieved netClient.lastSnapshotBaseID = snapshotID; - }catch(Exception e){ throw new RuntimeException(e); } } + public void readSnapshot(DataInputStream input) throws IOException{ + + //read wave info + state.wavetime = input.readFloat(); + state.wave = input.readInt(); + + byte cores = input.readByte(); + for(int i = 0; i < cores; i++){ + int pos = input.readInt(); + world.tile(pos).entity.items.read(input); + } + + long timestamp = input.readLong(); + + byte totalGroups = input.readByte(); + //for each group... + for(int i = 0; i < totalGroups; i++){ + //read group info + byte groupID = input.readByte(); + short amount = input.readShort(); + + EntityGroup group = Entities.getGroup(groupID); + + //go through each entity + for(int j = 0; j < amount; j++){ + int position = netClient.byteStream.position(); //save position to check read/write correctness + int id = input.readInt(); + byte typeID = input.readByte(); + + SyncTrait entity = (SyncTrait) group.getByID(id); + boolean add = false; + + //entity must not be added yet, so create it + if(entity == null){ + entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier + entity.resetID(id); + if(!netClient.isEntityUsed(entity.getID())){ + add = true; + } + } + + //read the entity + entity.read(input, timestamp); + + byte readLength = input.readByte(); + //if(netClient.byteStream.position() - position - 1 != readLength){ + // throw new RuntimeException("Error reading entity of type '" + group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1) + "]"); + //} + + if(add){ + entity.add(); + netClient.addRemovedEntity(entity.getID()); + } + } + } + } + @Override public void update(){ if(!Net.client()) return; diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index b7790d0809..3834ff5fce 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -37,6 +37,7 @@ import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Arrays; +import java.util.zip.DeflaterOutputStream; import static io.anuke.mindustry.Vars.*; @@ -171,7 +172,8 @@ public class NetServer extends Module{ //TODO try DeflaterOutputStream ByteArrayOutputStream stream = new ByteArrayOutputStream(); - NetworkIO.writeWorld(player, stream); + DeflaterOutputStream def = new DeflaterOutputStream(stream); + NetworkIO.writeWorld(player, def); WorldStream data = new WorldStream(); data.stream = new ByteArrayInputStream(stream.toByteArray()); Net.sendStream(id, data); @@ -401,6 +403,71 @@ public class NetServer extends Module{ admins.save(); } + public void writeSnapshot(Player player, DataOutputStream dataStream) throws IOException{ + //write wave datas + dataStream.writeFloat(state.wavetime); + dataStream.writeInt(state.wave); + + Array cores = state.teams.get(player.getTeam()).cores; + + dataStream.writeByte(cores.size); + + //write all core inventory data + for(Tile tile : cores){ + dataStream.writeInt(tile.packedPosition()); + tile.entity.items.write(dataStream); + } + + //write timestamp + dataStream.writeLong(TimeUtils.millis()); + + int totalGroups = 0; + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && (group.all().get(0) instanceof SyncTrait)) totalGroups++; + } + + //write total amount of serializable groups + dataStream.writeByte(totalGroups); + + //check for syncable groups + for(EntityGroup group : Entities.getAllGroups()){ + //TODO range-check sync positions to optimize? + if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; + + //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++; + } + } + + //write group ID + group size + dataStream.writeByte(group.getID()); + dataStream.writeShort(amount); + + for(Entity entity : group.all()){ + if(!((SyncTrait) entity).isSyncing()) continue; + + int position = syncStream.position(); + //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!"); + dataStream.writeByte(length); + } + } + } + String getUUID(int connectionID){ return connections.get(connectionID).uuid; } @@ -472,68 +539,7 @@ public class NetServer extends Module{ //reset stream to begin writing syncStream.reset(); - //write wave datas - dataStream.writeFloat(state.wavetime); - dataStream.writeInt(state.wave); - - Array cores = state.teams.get(player.getTeam()).cores; - - dataStream.writeByte(cores.size); - - //write all core inventory data - for(Tile tile : cores){ - dataStream.writeInt(tile.packedPosition()); - tile.entity.items.write(dataStream); - } - - //write timestamp - dataStream.writeLong(TimeUtils.millis()); - - int totalGroups = 0; - - for(EntityGroup group : Entities.getAllGroups()){ - if(!group.isEmpty() && (group.all().get(0) instanceof SyncTrait)) totalGroups++; - } - - //write total amount of serializable groups - dataStream.writeByte(totalGroups); - - //check for syncable groups - for(EntityGroup group : Entities.getAllGroups()){ - //TODO range-check sync positions to optimize? - if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; - - //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++; - } - } - - //write group ID + group size - dataStream.writeByte(group.getID()); - dataStream.writeShort(amount); - - for(Entity entity : group.all()){ - if(!((SyncTrait) entity).isSyncing()) continue; - - int position = syncStream.position(); - //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!"); - dataStream.writeByte(length); - } - } + writeSnapshot(player, dataStream); byte[] bytes = syncStream.toByteArray(); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 755d439faf..ae786b66f6 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -237,12 +237,9 @@ public class World extends Module{ EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); - Timers.mark(); generator.generateMap(tiles, sector); endMapLoad(); - - Log.info("Full time to generate: {0}", Timers.elapsed()); } public void loadMap(Map map){ diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 0fbab76305..167bd25740 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -129,6 +129,9 @@ public class NetworkIO{ } } + //now write a snapshot. + netServer.writeSnapshot(player, stream); + }catch(IOException e){ throw new RuntimeException(e); } @@ -270,6 +273,9 @@ public class NetworkIO{ world.endMapLoad(); + //read raw snapshot + netClient.readSnapshot(stream); + }catch(IOException e){ throw new RuntimeException(e); } diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index a8fbdc4630..6e6df2dbf9 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -38,13 +38,16 @@ import static io.anuke.ucore.util.Log.*; public class ServerControl extends Module{ private final CommandHandler handler = new CommandHandler(""); private ShuffleMode mode; + //consecutive sector losses + private int gameOvers; public ServerControl(String[] args){ Settings.defaultList( "shufflemode", "normal", "bans", "", "admins", "", - "sectorid", 0 + "sector_x", 0, + "sector_y", 1 ); mode = ShuffleMode.valueOf(Settings.getString("shufflemode")); @@ -105,7 +108,14 @@ public class ServerControl extends Module{ state.set(State.playing); } }else{ - info("Re-trying sector map."); + if(gameOvers >= 2){ + info("Two consecutive game-overs detected, shifting sector Y."); + Settings.putInt("sector_x", Settings.getInt("sector_x") - 1); + Settings.putInt("sector_y", Settings.getInt("sector_y") + 1); + Settings.save(); + } + info("Re-trying sector map: {0} {1}", Settings.getInt("sector_x"), Settings.getInt("sector_y")); + gameOvers ++; playSectorMap(); } }else{ @@ -183,7 +193,7 @@ public class ServerControl extends Module{ logic.play(); }else{ - Log.info("&ly&fiNo map specified. Loading sector {0}, {1}.", Settings.getInt("sectorid"), 0); + Log.info("&ly&fiNo map specified. Loading sector {0}, {1}.", Settings.getInt("sector_x"), Settings.getInt("sector_y")); playSectorMap(); } @@ -259,6 +269,17 @@ public class ServerControl extends Module{ } }); + handler.register("setsector ", "Sets the next sector to be played. Does not affect current game.", arg -> { + //TODO + try{ + Settings.putInt("sector_x", Integer.parseInt(arg[0])); + Settings.putInt("sector_y", Integer.parseInt(arg[1])); + Settings.save(); + }catch(NumberFormatException e){ + err("Invalid coordinates."); + } + }); + handler.register("fillitems", "Fill the core with 2000 items.", arg -> { if(!state.is(State.playing)){ err("Not playing. Host first."); @@ -623,9 +644,8 @@ public class ServerControl extends Module{ return; } + info("&lyCore destroyed."); Events.fire(GameOverEvent.class); - - info("Core destroyed."); }); handler.register("debuginfo", "Print debug info", arg -> { @@ -825,7 +845,11 @@ public class ServerControl extends Module{ } private void playSectorMap(){ - world.loadSector(world.sectors().get(Settings.getInt("sectorid"), 0)); + int x = Settings.getInt("sector_x"), y = Settings.getInt("sector_y"); + if(world.sectors().get(x, y) == null){ + world.sectors().createSector(x, y); + } + world.loadSector(world.sectors().get(x, y)); logic.play(); } @@ -846,7 +870,9 @@ public class ServerControl extends Module{ if(world.getSector().completedMissions >= world.getSector().missions.size){ world.sectors().completeSector(world.getSector().x, world.getSector().y); world.sectors().save(); - Settings.putInt("sectorid", world.getSector().x + world.getSector().size); + gameOvers = 0; + Settings.putInt("sector_x", world.getSector().x + world.getSector().size); + Settings.save(); netServer.kickAll(KickReason.sectorComplete);