WIP save refactoring

This commit is contained in:
Anuken
2019-05-05 11:36:38 -04:00
parent 13969bdd29
commit af67690e75
20 changed files with 387 additions and 44 deletions

View File

@@ -7,7 +7,7 @@ import io.anuke.arc.collection.IntSet;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.RandomXS128;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.ReusableByteArrayInputStream;
import io.anuke.arc.util.io.ReusableByteInStream;
import io.anuke.arc.util.serialization.Base64Coder;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State;
@@ -50,7 +50,7 @@ public class NetClient implements ApplicationListener{
/** 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 ReusableByteInStream byteStream = new ReusableByteInStream();
private DataInputStream dataStream = new DataInputStream(byteStream);
public NetClient(){

View File

@@ -13,7 +13,7 @@ import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.ByteBufferOutput;
import io.anuke.arc.util.io.CountableByteArrayOutputStream;
import io.anuke.arc.util.io.ReusableByteOutStream;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Entities;
@@ -56,7 +56,7 @@ public class NetServer implements ApplicationListener{
private ByteBufferOutput outputBuffer = new ByteBufferOutput(writeBuffer);
/** Stream for writing player sync data to. */
private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream();
private ReusableByteOutStream syncStream = new ReusableByteOutStream();
/** Data stream for writing player sync data to. */
private DataOutputStream dataStream = new DataOutputStream(syncStream);

View File

@@ -1,7 +1,6 @@
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.Events;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.ObjectSet;
@@ -115,18 +114,14 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
return dead || tile.entity != this;
}
@CallSuper
public void write(DataOutput stream) throws IOException{
}
public void writeConfig(DataOutput stream) throws IOException{
}
@CallSuper
public void read(DataInput stream) throws IOException{
}
public void readConfig(DataInput stream) throws IOException{
}
public boolean collide(Bullet other){
return true;
}

View File

@@ -171,7 +171,9 @@ public class MapIO{
}else if(tile.entity != null){
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
stream.writeShort(/*(short)tile.entity.health*/tile.block().health); //health
tile.entity.writeConfig(stream);
if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){
stream.writeByte(-1); //write an meaningless byte here, just a fallback thing
}
}else{
//write consecutive non-entity blocks
int consecutives = 0;
@@ -305,7 +307,9 @@ public class MapIO{
tile.entity.health = /*health*/tile.block().health;
tile.setRotation(rotation);
tile.entity.readConfig(stream);
if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){
stream.readByte(); //these blocks have an extra config byte, read it
}
}else{ //no entity/part, read consecutives
int consecutives = stream.readUnsignedByte();

View File

@@ -1,8 +1,9 @@
package io.anuke.mindustry.io;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.collection.*;
import io.anuke.arc.collection.ObjectMap.Entry;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.io.ReusableByteOutStream;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.EntityGroup;
@@ -18,8 +19,21 @@ import java.io.*;
import static io.anuke.mindustry.Vars.content;
import static io.anuke.mindustry.Vars.world;
/**
* Format:
*
* Everything is compressed. Use a DeflaterStream to begin reading.
*
* 1. version of format / int
* 2. meta tags
* - length / short
* - continues with (string, string) pairs indicating key, value
*/
public abstract class SaveFileVersion{
public final int version;
private final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
private final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
private final ObjectMap<String, String> fallback = ObjectMap.of(
"alpha-dart-mech-pad", "dart-mech-pad"
);
@@ -28,6 +42,42 @@ public abstract class SaveFileVersion{
this.version = version;
}
/** Write a chunk of input to the stream. An integer of some length is written first, followed by the data. */
public void writeChunk(DataOutput output, boolean isByte, IORunner<DataOutput> runner) throws IOException{
//reset output position
byteOutput.position(0);
//writer the needed info
runner.accept(dataBytes);
int length = byteOutput.position();
//write length (either int or byte) followed by the output bytes
if(!isByte){
output.writeInt(length);
}else{
if(length > 255){
throw new IOException("Byte write length exceeded: " + length + " > 255");
}
output.writeByte(length);
}
output.write(byteOutput.getBytes(), 0, length);
}
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
public int readChunk(DataInput input, boolean isByte, IORunner<DataInput> runner) throws IOException{
int length = isByte ? input.readUnsignedByte() : input.readInt();
//TODO descriptive error with chunk name
runner.accept(input);
return length;
}
/** Skip a chunk completely. */
public void skipChunk(DataInput input, boolean isByte) throws IOException{
int length = readChunk(input, isByte, t -> {});
int skipped = input.skipBytes(length);
if(length != skipped){
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
}
}
public SaveMeta getData(DataInputStream stream) throws IOException{
long time = stream.readLong();
long playtime = stream.readLong();
@@ -39,6 +89,23 @@ public abstract class SaveFileVersion{
return new SaveMeta(version, time, playtime, build, map, wave, rules);
}
public void writeMeta(DataOutputStream stream, ObjectMap<String, String> map) throws IOException{
stream.writeShort(map.size);
for(Entry<String, String> entry : map.entries()){
stream.writeUTF(entry.key);
stream.writeUTF(entry.value);
}
}
public StringMap readMeta(DataInputStream stream) throws IOException{
StringMap map = new StringMap();
short size = stream.readShort();
for(int i = 0; i < size; i++){
map.put(stream.readUTF(), stream.readUTF());
}
return map;
}
public void writeMap(DataOutputStream stream) throws IOException{
//write world size
stream.writeShort(world.width());
@@ -81,7 +148,6 @@ public abstract class SaveFileVersion{
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
if(tile.entity.cons != null) tile.entity.cons.write(stream);
tile.entity.writeConfig(stream);
tile.entity.write(stream);
}else{
//write consecutive non-entity blocks
@@ -157,7 +223,6 @@ public abstract class SaveFileVersion{
if(tile.entity.liquids != null) tile.entity.liquids.read(stream);
if(tile.entity.cons != null) tile.entity.cons.read(stream);
tile.entity.readConfig(stream);
tile.entity.read(stream);
}else{
int consecutives = stream.readUnsignedByte();
@@ -255,4 +320,8 @@ public abstract class SaveFileVersion{
public abstract void read(DataInputStream stream) throws IOException;
public abstract void write(DataOutputStream stream) throws IOException;
public interface IORunner<T>{
void accept(T stream) throws IOException;
}
}

View File

@@ -11,9 +11,7 @@ import java.util.zip.InflaterInputStream;
import static io.anuke.mindustry.Vars.*;
//TODO load backup meta if possible
public class SaveIO{
public static final IntArray breakingVersions = IntArray.with(47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63);
public static final IntMap<SaveFileVersion> versions = new IntMap<>();
public static final Array<SaveFileVersion> versionArray = Array.with(new Save1());
@@ -30,11 +28,11 @@ public class SaveIO{
public static void saveToSlot(int slot){
FileHandle file = fileFor(slot);
boolean exists = file.exists();
if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension()));
if(exists) file.moveTo(backupFileFor(file));
try{
write(fileFor(slot));
}catch(Exception e){
if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file);
if(exists) backupFileFor(file).moveTo(file);
throw new RuntimeException(e);
}
}
@@ -47,12 +45,12 @@ public class SaveIO{
return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize)));
}
public static DataInputStream getBackupSlotStream(int slot){
return new DataInputStream(new InflaterInputStream(backupFileFor(fileFor(slot)).read(bufferSize)));
}
public static boolean isSaveValid(int slot){
try{
return isSaveValid(getSlotStream(slot));
}catch(Exception e){
return false;
}
return isSaveValid(getSlotStream(slot)) || isSaveValid(getBackupSlotStream(slot));
}
public static boolean isSaveValid(FileHandle file){
@@ -60,7 +58,6 @@ public class SaveIO{
}
public static boolean isSaveValid(DataInputStream stream){
try{
getData(stream);
return true;
@@ -90,6 +87,10 @@ public class SaveIO{
return saveDirectory.child(slot + "." + Vars.saveExtension);
}
public static FileHandle backupFileFor(FileHandle file){
return file.sibling(file.name() + "-backup." + file.extension());
}
public static void write(FileHandle file){
write(new DeflaterOutputStream(file.write(false, bufferSize)){
byte[] tmp = {0};

View File

@@ -185,12 +185,7 @@ public class LoadDialog extends FloatingDialog{
button.clicked(() -> {
if(!button.childrenPressed()){
int build = slot.getBuild();
if(SaveIO.breakingVersions.contains(build)){
ui.showInfo("$save.old");
slot.delete();
}else{
runLoadSave(slot);
}
runLoadSave(slot);
}
});
}

View File

@@ -1,8 +1,11 @@
package io.anuke.mindustry.world;
import io.anuke.arc.util.*;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.type.Item;
import java.io.*;
import static io.anuke.mindustry.Vars.content;
public class ItemBuffer{
@@ -57,4 +60,23 @@ public class ItemBuffer{
System.arraycopy(buffer, 1, buffer, 0, index - 1);
index--;
}
public void write(DataOutput stream) throws IOException{
stream.writeByte((byte)index);
stream.writeByte((byte)buffer.length);
for(long l : buffer){
stream.writeLong(l);
}
}
public void read(DataInput stream) throws IOException{
index = stream.readByte();
byte length = stream.readByte();
for(int i = 0; i < length; i++){
long l = stream.readLong();
if(i < buffer.length){
buffer[i] = l;
}
}
}
}

View File

@@ -24,9 +24,11 @@ public class Tile implements Position, TargetTrait{
public short x, y;
protected Block block;
protected Floor floor;
/** Rotation, 0-3. Also used to store offload location, in which case it can be any number. */
/** Rotation, 0-3. Also used to store offload location and link, in which case it can be any number.
* When saved in non-link form, this data is truncated to 4 bits = max 16.*/
private byte rotation;
/** Team ordinal. */
/** Team ordinal. Keep in mind that this is written as 4 bits, which means that there are only 2^4 = 16 possible teams.
* Complications may arise from using signed bytes as well. Be careful.*/
private byte team;
/** Ore that is on top of this (floor) block. */
private byte overlay = 0;

View File

@@ -6,6 +6,8 @@ import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.ItemBuffer;
import io.anuke.mindustry.world.Tile;
import java.io.*;
public class BufferedItemBridge extends ExtendingItemBridge{
protected int timerAccept = timers++;
@@ -43,5 +45,17 @@ public class BufferedItemBridge extends ExtendingItemBridge{
class BufferedItemBridgeEntity extends ItemBridgeEntity{
ItemBuffer buffer = new ItemBuffer(bufferCapacity, speed);
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
buffer.write(stream);
}
@Override
public void read(DataInput stream) throws IOException{
super.read(stream);
buffer.read(stream);
}
}
}

View File

@@ -7,6 +7,8 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockGroup;
import java.io.*;
import static io.anuke.mindustry.Vars.content;
public class Junction extends Block{
@@ -82,6 +84,20 @@ public class Junction extends Block{
class JunctionEntity extends TileEntity{
Buffer[] buffers = {new Buffer(), new Buffer(), new Buffer(), new Buffer()};
@Override
public void write(DataOutput stream) throws IOException{
for(Buffer b : buffers){
b.write(stream);
}
}
@Override
public void read(DataInput stream) throws IOException{
for(Buffer b : buffers){
b.read(stream);
}
}
}
class Buffer{
@@ -96,5 +112,24 @@ public class Junction extends Block{
boolean full(){
return index >= items.length - 1;
}
void write(DataOutput stream) throws IOException{
stream.writeByte((byte)index);
stream.writeByte((byte)items.length);
for(long l : items){
stream.writeLong(l);
}
}
void read(DataInput stream) throws IOException{
index = stream.readByte();
byte length = stream.readByte();
for(int i = 0; i < length; i++){
long l = stream.readLong();
if(i < items.length){
items[i] = l;
}
}
}
}
}

View File

@@ -132,12 +132,12 @@ public class Sorter extends Block{
public Item sortItem;
@Override
public void writeConfig(DataOutput stream) throws IOException{
public void write(DataOutput stream) throws IOException{
stream.writeByte(sortItem == null ? -1 : sortItem.id);
}
@Override
public void readConfig(DataInput stream) throws IOException{
public void read(DataInput stream) throws IOException{
byte b = stream.readByte();
sortItem = b == -1 ? null : content.items().get(b);
}

View File

@@ -116,12 +116,12 @@ public class LiquidSource extends Block{
public Liquid source = null;
@Override
public void writeConfig(DataOutput stream) throws IOException{
public void write(DataOutput stream) throws IOException{
stream.writeByte(source == null ? -1 : source.id);
}
@Override
public void readConfig(DataInput stream) throws IOException{
public void read(DataInput stream) throws IOException{
byte id = stream.readByte();
source = id == -1 ? null : content.liquid(id);
}

View File

@@ -104,12 +104,12 @@ public class Unloader extends Block{
public Item sortItem = null;
@Override
public void writeConfig(DataOutput stream) throws IOException{
public void write(DataOutput stream) throws IOException{
stream.writeByte(sortItem == null ? -1 : sortItem.id);
}
@Override
public void readConfig(DataInput stream) throws IOException{
public void read(DataInput stream) throws IOException{
byte id = stream.readByte();
sortItem = id == -1 ? null : content.items().get(id);
}

View File

@@ -197,14 +197,15 @@ public class UnitFactory extends Block{
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(buildTime);
stream.writeInt(spawned);
}
@Override
public void read(DataInput stream) throws IOException{
super.read(stream);
buildTime = stream.readFloat();
stream.readFloat(); //unneeded information, will remove later
spawned = stream.readInt();
}
}