1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-22 13:57:46 +03:00

Updated saving to DataOutput/Input

This commit is contained in:
Anuken 2018-06-07 20:30:02 -04:00
parent 9aa5460688
commit 2bcbc3294b
19 changed files with 82 additions and 966 deletions

View File

@ -19,7 +19,6 @@ 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 int maxRequests = 50;
private Timer timer = new Timer(5);
/**Whether the client is currently conencting.*/

View File

@ -13,6 +13,8 @@ import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Administration.PlayerInfo;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Timer;
@ -169,6 +171,18 @@ public class NetServer extends Module{
void sync(){
//TODO implement snapshot packets w/ delta compression
//iterate through each player
for(Player player : connections.values()){
//check for syncable groups.
for(EntityGroup<?> group : Entities.getAllGroups()){
if(group.isEmpty()) continue;
}
}
}
@Remote(server = false)

View File

@ -30,10 +30,7 @@ import io.anuke.ucore.graphics.Fill;
import io.anuke.ucore.graphics.Lines;
import io.anuke.ucore.util.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.io.*;
import static io.anuke.mindustry.Vars.*;
@ -574,7 +571,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait {
//region read and write methods
@Override
public void writeSave(DataOutputStream stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException {
stream.writeBoolean(isLocal);
if(isLocal){
@ -589,7 +586,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait {
}
@Override
public void readSave(DataInputStream stream) throws IOException {
public void readSave(DataInput stream) throws IOException {
boolean local = stream.readBoolean();
if(local){
@ -598,7 +595,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait {
}
}
private void readSaveSuper(DataInputStream stream) throws IOException {
private void readSaveSuper(DataInput stream) throws IOException {
super.readSave(stream);
byte uamount = stream.readByte();
@ -610,12 +607,12 @@ public class Player extends Unit implements BuilderTrait, CarryTrait {
}
@Override
public void write(ByteBuffer buffer) {
public void write(DataOutput buffer) {
//todo
}
@Override
public void read(ByteBuffer buffer, long time) {
public void read(DataInput buffer, long time) {
//todo
}

View File

@ -19,8 +19,8 @@ import io.anuke.ucore.entities.trait.SolidTrait;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.state;
@ -96,7 +96,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
@Override
public void writeSave(DataOutputStream stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException {
stream.writeByte(team.ordinal());
stream.writeFloat(x);
stream.writeFloat(y);
@ -107,7 +107,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
@Override
public void readSave(DataInputStream stream) throws IOException {
public void readSave(DataInput stream) throws IOException {
byte team = stream.readByte();
float x = stream.readFloat();
float y = stream.readFloat();

View File

@ -6,9 +6,7 @@ import io.anuke.mindustry.type.AmmoType;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.*;
public class UnitInventory {
private Array<AmmoEntry> ammos = new Array<>();
@ -31,7 +29,7 @@ public class UnitInventory {
infiniteAmmo = infinite;
}
public void write(DataOutputStream stream) throws IOException {
public void write(DataOutput stream) throws IOException {
stream.writeInt(item == null ? 0 : item.amount);
stream.writeByte(item == null ? 0 : item.item.id);
stream.writeBoolean(infiniteAmmo);
@ -43,7 +41,7 @@ public class UnitInventory {
}
}
public void read(DataInputStream stream) throws IOException {
public void read(DataInput stream) throws IOException {
int iamount = stream.readInt();
byte iid = stream.readByte();
infiniteAmmo = stream.readBoolean();

View File

@ -15,7 +15,9 @@ import io.anuke.ucore.entities.trait.SolidTrait;
import io.anuke.ucore.entities.trait.VelocityTrait;
import io.anuke.ucore.util.Timer;
import java.nio.ByteBuffer;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.bulletGroup;
import static io.anuke.mindustry.Vars.world;
@ -70,19 +72,19 @@ public class Bullet extends BulletEntity<BulletType> implements TeamTrait, SyncT
}
@Override
public void write(ByteBuffer data) {
data.putFloat(x);
data.putFloat(y);
data.put((byte)team.ordinal());
data.put((byte)type.id);
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeByte(team.ordinal());
data.writeByte(type.id);
}
@Override
public void read(ByteBuffer data, long time) {
x = data.getFloat();
y = data.getFloat();
team = Team.values()[data.get()];
type = BulletType.getByID(data.get());
public void read(DataInput data, long time) throws IOException{
x = data.readFloat();
y = data.readFloat();
team = Team.values()[data.readByte()];
type = BulletType.getByID(data.readByte());
}
@Override

View File

@ -18,8 +18,8 @@ import io.anuke.ucore.entities.impl.TimedEntity;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
@ -115,14 +115,14 @@ public class Fire extends TimedEntity implements SaveTrait, Poolable {
}
@Override
public void writeSave(DataOutputStream stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException {
stream.writeInt(tile.packedPosition());
stream.writeFloat(lifetime);
stream.writeFloat(time);
}
@Override
public void readSave(DataInputStream stream) throws IOException {
public void readSave(DataInput stream) throws IOException {
this.loadedPosition = stream.readInt();
this.lifetime = stream.readFloat();
this.time = stream.readFloat();

View File

@ -20,7 +20,9 @@ import io.anuke.ucore.entities.trait.VelocityTrait;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.Mathf;
import java.nio.ByteBuffer;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.itemGroup;
import static io.anuke.mindustry.Vars.itemSize;
@ -156,16 +158,16 @@ public class ItemDrop extends SolidEntity implements SyncTrait, DrawTrait, Veloc
}
@Override
public void write(ByteBuffer data) {
data.putFloat(x);
data.putFloat(y);
data.put((byte)item.id);
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeByte(item.id);
}
@Override
public void read(ByteBuffer data, long time) {
x = data.getFloat();
y = data.getFloat();
item = Item.getByID(data.get());
public void read(DataInput data, long time) throws IOException{
x = data.readFloat();
y = data.readFloat();
item = Item.getByID(data.readByte());
}
}

View File

@ -29,8 +29,8 @@ import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.puddleGroup;
@ -202,7 +202,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void writeSave(DataOutputStream stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException {
stream.writeInt(tile.packedPosition());
stream.writeFloat(x);
stream.writeFloat(y);
@ -212,7 +212,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void readSave(DataInputStream stream) throws IOException {
public void readSave(DataInput stream) throws IOException {
this.loadedPosition = stream.readInt();
this.x = stream.readFloat();
this.y = stream.readFloat();

View File

@ -2,12 +2,12 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.ucore.entities.trait.Entity;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**Marks an entity as serializable.*/
public interface SaveTrait extends Entity{
void writeSave(DataOutputStream stream) throws IOException;
void readSave(DataInputStream stream) throws IOException;
void writeSave(DataOutput stream) throws IOException;
void readSave(DataInput stream) throws IOException;
}

View File

@ -4,7 +4,9 @@ import com.badlogic.gdx.Gdx;
import io.anuke.mindustry.net.Interpolator;
import io.anuke.ucore.entities.trait.Entity;
import java.nio.ByteBuffer;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.threads;
@ -36,6 +38,6 @@ public interface SyncTrait extends Entity {
Interpolator getInterpolator();
//Read and write sync data, usually position
void write(ByteBuffer data);
void read(ByteBuffer data, long time);
void write(DataOutput data) throws IOException;
void read(DataInput data, long time) throws IOException;
}

View File

@ -21,10 +21,7 @@ import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Timer;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.io.*;
import static io.anuke.mindustry.Vars.*;
@ -236,13 +233,13 @@ public abstract class BaseUnit extends Unit{
}
@Override
public void writeSave(DataOutputStream stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException {
super.writeSave(stream);
stream.writeByte(type.id);
}
@Override
public void readSave(DataInputStream stream) throws IOException {
public void readSave(DataInput stream) throws IOException {
super.readSave(stream);
byte type = stream.readByte();
@ -251,12 +248,12 @@ public abstract class BaseUnit extends Unit{
}
@Override
public void write(ByteBuffer data) {
public void write(DataOutput data) {
//todo
}
@Override
public void read(ByteBuffer data, long time) {
public void read(DataInput data, long time) {
//todo
}
}

View File

@ -7,7 +7,9 @@ import io.anuke.mindustry.gen.RemoteReadClient;
import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Packet.ImportantPacket;
import io.anuke.mindustry.net.Packet.UnimportantPacket;
import io.anuke.ucore.util.IOUtils;
import io.anuke.ucore.io.ByteBufferOutput;
import io.anuke.ucore.io.IOUtils;
import io.anuke.ucore.io.delta.ByteBufferInput;
import java.nio.ByteBuffer;
@ -101,17 +103,21 @@ public class Packets {
@Override
public void write(ByteBuffer buffer) {
ByteBufferOutput out = new ByteBufferOutput(buffer);
buffer.putInt(player.id);
buffer.putLong(TimeUtils.millis());
player.write(buffer);
player.write(out);
}
@Override
public void read(ByteBuffer buffer) {
ByteBufferInput in = new ByteBufferInput(buffer);
int id = buffer.getInt();
long time = buffer.getLong();
player = Vars.playerGroup.getByID(id);
player.read(buffer, time);
player.read(in, time);
}
}

View File

@ -1,101 +0,0 @@
/*
* Copyright (C) 2015 Michael Zucchi
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.anuke.mindustry.net.delta;
/**
* The interface for encoding a delta.
* <p>
* A delta encoder will implement a specific file/transfer format.
*/
public interface ByteDeltaEncoder {
/**
* Initialises creating a new patch.
*
* @param sourceSize
* @param targetSize
*/
public void init(int sourceSize, int targetSize);
/**
* Appends a copy command.
*
* @param addr
* @param len
*/
public void copy(int addr, int len);
/**
* Appends an append command.
*
* @param data
* @param off
* @param len
*/
public void add(byte[] data, int off, int len);
/**
* Appends a byte-run.
*
* @param b
* @param len
*/
public void run(byte b, int len);
/**
* Retrieves the patch.
*
* @return
*/
public byte[] toPatch();
/**
* Creates a delta from a matcher and writes it to an encoder.
*
* @param matcher
* @param enc
* @return
*/
public static byte[] toDiff(ByteMatcher matcher, ByteDeltaEncoder enc) {
byte[] source = matcher.getSource();
byte[] target = matcher.getTarget();
enc.init(source.length, target.length);
int targetEnd = 0;
int state;
while ((state = matcher.nextMatch()) != ByteMatcher.EOF) {
int toff = matcher.getTargetOffset();
int slength = matcher.getLength();
if (targetEnd != toff)
enc.add(target, targetEnd, toff - targetEnd);
if (state == ByteMatcher.RUN)
enc.run(matcher.getRunByte(), slength);
else
enc.copy(matcher.getMatchOffset(), slength);
targetEnd = toff + slength;
}
if (targetEnd != target.length)
enc.add(target, targetEnd, target.length - targetEnd);
return enc.toPatch();
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (C) 2015 Michael Zucchi
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.anuke.mindustry.net.delta;
/**
* Common interface for byte matchers.
* <p>
* Byte matchers look for common sub-strings between a source and
* a target byte array and may optionally detect runs of duplicated
* bytes.
*/
public interface ByteMatcher {
public final static int COPY = 0;
public final static int RUN = 1;
public static final int EOF = -1;
/**
* Finds the next match or run.
* <p>
* Note that only matches or byte runs will be indicated. The location
* of non-matching data (i.e. append sequences) must be determined from
* the difference between the last targetOffset, the last length, and the
* current targetOffset.
* </p>
*
* @return the new state.
*/
public int nextMatch();
/**
* Retrieves the current target position.
* <p>
* The position within the target to which the current match refers.
*
* @return
*/
public int getTargetOffset();
/**
* Retrieves the best match location.
* <p>
* If the current state is COPY then this returns a valid location
* of the best match. This should be interpreted
* using {@link #getBlockArray} and {@link #getBlockOffset}.
*
* @return
*/
public int getMatchOffset();
/**
* Retrieves the byte to be run-length encoded.
* <p>
* If the current state is RUN then this returns the corresponding byte to run.
*
* @return
*/
public byte getRunByte();
/**
* Retrieves the current length.
* <p>
* This is the number of bytes to copy for the COPY state or repeat for the RUN state.
*
* @return
*/
public int getLength();
/**
* Retrieves the array containing the current match.
* <p>
* Maps the offset to the correct internal array.
*
* @param offset
* @return
* @see #getBlockOffset
*/
public byte[] getBlockArray(int offset);
/**
* Calculates the offset for the block array.
* <p>
* Maps the match offset to the array from <code>getBlockArray</code>.
*
* @param offset
* @return
* @see #getBlockArray
*/
public int getBlockOffset(int offset);
public byte[] getSource();
public byte[] getTarget();
}

View File

@ -1,300 +0,0 @@
/*
* Copyright (C) 2015 Michael Zucchi
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.anuke.mindustry.net.delta;
import java.util.Arrays;
/**
* Finds common strings of bytes between a source and target buffer.
* <p>
* This is basically an implementation of Bentley &amp; McIllroy's paper
* ``Data Compression Using Long Common Strings'' applied instead to producing
* deltas and using a cyclic hash as the fingerprint function.
* <p>
* Two other
* modifications are that back-tracking is not implemented but instead
* overlapping blocks can be used by setting the step size.
* And a further refinement is the detection of runs of the same byte which
* might otherwise pollute the hash tree for certain data.
*/
public class ByteMatcherHash implements ByteMatcher {
private final int b;
private final int shortest;
private final byte[] source;
private final int sstep;
private final byte[] target;
// Incremental hashes
private final CyclicHash targetHash;
private final CyclicHash sourceHash;
// Runtime state
private int ti;
private int thash;
private int skipTo;
private int targetAvailable;
// Public state
private int bestLength;
private int bestOffset;
private int targetOffset;
private byte runByte;
/**
* Inline hash+array table.
* <p>
* All values which hash the same are appended to the same list.
* <p>
* Index is the current length/next insertion point for that hash chain.
* Values contains the chained hash table values.
*/
final private int hashMask;
final private int[][] hashValues;
/**
* Creates and initialises a new byte matcher.
* <p>
* This is a single-use object.
* <p>
* A step size of 1 produces the best output but requires the most memory and run time.
* <p>
* @param b Sets block size, which is the number of bytes hashed per key (&amp;=3).
* @param shortest shortest string considered for a copy. Typically 4 bytes but dependent on the encoder used and
* the value of b.
* @param source Source array.
* @param sstep Sets the step size which is the interval of sampling of the source.
* @param target Target array.
*/
public ByteMatcherHash(int b, int shortest, byte[] source, int sstep, byte[] target) {
int size;
b = Math.max(b, 3);
// This may need tuning.
int logN = 31 - Integer.numberOfLeadingZeros((source.length + target.length) / sstep);
size = 1 << Math.max(14, logN - 5);
hashMask = size - 1;
hashValues = new int[size][];
targetHash = new CyclicHash(b);
sourceHash = new CyclicHash(b);
this.b = b;
this.shortest = shortest;
this.source = source;
this.sstep = sstep;
this.target = target;
addAll(source, source.length, 0, 0);
if (target.length >= b)
this.thash = targetHash.init(target, 0);
}
/**
* Checks for run of 3 bytes.
* <p>
* Boundaries are not checked.
*
* @param s
* @param pos
* @return
*/
private boolean isRun(byte[] s, int pos) {
byte v = s[pos];
return v == s[pos + 1] && v == s[pos + 2];
}
private int addAll(byte[] s, int limit, int pos, int off) {
if (sstep == 1) {
if (pos == 0 && limit >= b) {
add(sourceHash.init(s, 0), off);
pos = 1;
}
while (pos <= limit - b) {
int hash = sourceHash.update(s[pos - 1], s[pos - 1 + b]);
if (!isRun(s, pos))
add(hash, pos + off);
pos += 1;
}
} else {
while (pos <= limit - b) {
if (!isRun(s, pos))
add(sourceHash.init(s, pos), pos + off);
pos += sstep;
}
}
return pos;
}
private void add(int hash, int value) {
int j = hash & hashMask;
int[] vs = hashValues[j];
if (vs == null) {
hashValues[j] = vs = new int[4];
vs[0] = 2;
vs[1] = value;
} else {
int i = vs[0];
if (i >= vs.length)
hashValues[j] = vs = Arrays.copyOf(vs, vs.length * 2);
vs[i++] = value;
vs[0] = i;
}
}
/**
* Finds the length of similarity between the two sub-arrays.
* <p>
*
* @param soff source offset starting location, locations above source.length refer to the target buffer.
* @param toff target offset starting location
* @return how many bytes are sequentially identical.
*/
private int matchLength(int soff, int toff) {
if (soff < source.length) {
int limit = Math.min(source.length - soff, target.length - toff);
for (int i = 0; i < limit; i++)
if (source[soff + i] != target[toff + i])
return i;
return limit;
} else {
soff -= source.length;
int limit = Math.min(target.length - soff, target.length - toff);
for (int i = 0; i < limit; i++)
if (target[soff + i] != target[toff + i])
return i;
return limit;
}
}
@Override
public byte[] getSource() {
return source;
}
@Override
public byte[] getTarget() {
return target;
}
@Override
public int getMatchOffset() {
return bestOffset;
}
@Override
public int getTargetOffset() {
return targetOffset;
}
@Override
public int getLength() {
return bestLength;
}
@Override
public byte getRunByte() {
return runByte;
}
@Override
public int nextMatch() {
bestLength = 0;
bestOffset = 0;
/**
* Reset thash on seek.
*/
if (skipTo != ti) {
if (skipTo <= target.length - b)
thash = targetHash.init(target, skipTo);
ti = skipTo;
}
while (bestLength < shortest && ti <= target.length - b) {
/**
* short circuit test for byte-runs.
*/
if (isRun(target, ti)) {
byte b0 = target[ti];
int j = ti + 3;
while (j < target.length && target[j] == b0)
j++;
targetOffset = ti;
bestLength = j - ti;
runByte = b0;
skipTo = j;
return RUN;
}
/**
* Include any of the target buffer which has been decoded to this point.
*/
targetAvailable = addAll(target, ti + b - 1, targetAvailable, source.length);
/**
* Checks the current string for the longest match.
*/
int j = thash & hashMask;
int[] soffs = hashValues[j];
if (soffs != null) {
int len = soffs[0];
for (int i = 1; i < len; i++) {
int soff = soffs[i];
int length = matchLength(soff, ti);
if (length > bestLength) {
bestLength = length;
bestOffset = soff;
}
}
}
/**
* Advance. thash is always the next block to examine.
*/
targetOffset = ti;
ti += 1;
if (ti <= target.length - b)
thash = targetHash.update(target[ti - 1], target[ti - 1 + b]);
}
if (bestLength >= shortest) {
skipTo = targetOffset + bestLength;
return COPY;
} else
return EOF;
}
@Override
public byte[] getBlockArray(int offset) {
return (offset < source.length) ? source : target;
}
@Override
public int getBlockOffset(int offset) {
return (offset < source.length) ? offset : offset - source.length;
}
}

View File

@ -1,90 +0,0 @@
/*
* Copyright (C) 2015 Michael Zucchi
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.anuke.mindustry.net.delta;
import java.util.Random;
import static java.lang.Integer.rotateLeft;
/**
* Cyclic polynomial rolling hash.
* <p>
* This implements a rolling hash of a fixed length.
* <p>
* Input bytes are hashed using a random table. The randomness
* affects the quality of the hash.
*/
public class CyclicHash {
private static final int[] random;
private final int b;
private int hash;
private final int first;
static {
// keyboard bashed the results unvalidated.
Random r = new Random(97435);
random = new int[256];
for(int i = 0; i < random.length; i ++){
random[i] = r.nextInt();
}
}
/**
* Creates a cyclic hash.
*
* @param b
*/
public CyclicHash(int b) {
this.b = b;
this.first = ((b - 1) * 9) & 31;
}
/**
* Initialises the hash.
* <p>
* This will hash a block of data at the given location.
*
* @param data
* @param off
* @return
*/
public int init(byte[] data, int off) {
hash = 0;
for (int i = 0; i < b; i++)
hash = rotateLeft(hash, 9) ^ random[data[i + off] & 0xff];
return hash;
}
/**
* Updates the hash incrementally.
* <p>
* Advance the hash by one location.
*
* @param leave the byte leaving. Must match the oldest byte included in the hash value.
* @param enter the byte entering.
* @return
*/
public int update(byte leave, byte enter) {
int leaving = rotateLeft(random[leave & 0xff], first);
int entering = random[enter & 0xff];
hash = rotateLeft(hash ^ leaving, 9) ^ entering;
return hash;
}
}

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2015 Michael Zucchi
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.anuke.mindustry.net.delta;
import java.io.IOException;
/**
* An in-memory DeltaZ-1 decoder.
* <p>
* Transforms a source and patch into a target.
* <p>
*/
public class DEZDecoder {
private final byte[] patch;
private final byte[] source;
private int pi, si;
public DEZDecoder(byte[] src, byte[] patch) {
this.patch = patch;
this.source = src;
}
private int decodeInt() {
int v = 0;
byte b;
int limit = Math.min(patch.length, pi + 5);
do {
b = patch[pi++];
v = (v << 7) | (b & 0x7f);
} while (pi < limit && (b & 0x80) != 0);
return v;
}
/**
* On entry pi points to the opcode, which is also in 'op'.
*
* @param op
* @return
*/
private int decodeLength(int op) {
int length = op & 0x1f;
pi++;
while ((op & 0x80) != 0) {
op = patch[pi++];
length = (length << 7) | (op & 0x7f);
}
return length;
}
/**
* Recreates the original target data from the source and patch.
*
* @return
* @throws IOException
*/
public byte[] decode() throws IOException, ArrayIndexOutOfBoundsException {
byte[] target;
int ti = 0;
pi = 0;
si = 0;
// 'decode' magic
for (int i = 0; i < DEZEncoder.MAGIC.length; i++)
if (patch[i] != DEZEncoder.MAGIC[i])
throw new IOException("Invalid magic");
pi += 4;
// 'decode' flags
if (patch[pi] != 0)
throw new IOException("Unknown flags");
pi += 1;
// get sizes
int sourceSize = decodeInt();
int targetSize = decodeInt();
if (sourceSize != source.length)
throw new IOException("Patch/source size mismatch");
target = new byte[targetSize];
/**
* Decode loop.
* <p>
* Since java will check the array accesses anyway, don't clutter the code with our own.
*/
while (ti < targetSize) {
byte op = patch[pi];
byte r;
if ((op & 0x40) == 0) {
// COPY
int length = decodeInt();
int addr = decodeInt();
if (addr < sourceSize)
for (int i = 0; i < length; i++)
target[ti++] = source[addr + i];
else
for (int i = 0; i < length; i++)
target[ti++] = target[addr - sourceSize + i];
} else if ((op & 0x20) == 0) {
// ADD
int length = decodeLength(op);
for (int i = 0; i <= length; i++)
target[ti++] = patch[pi++];
} else {
// RUN
int length = decodeLength(op);
r = patch[pi++];
for (int i = 0; i <= length; i++)
target[ti++] = r;
}
}
return target;
}
}

View File

@ -1,161 +0,0 @@
/*
* Copyright (C) 2015 Michael Zucchi
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.anuke.mindustry.net.delta;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* 'DeltaZ-1' format encoder.
* <p>
* Encoder for very simple binary delta format.
* <p>
* <h3>Header</h3>
* <pre>
* magic: 'D' 'E' 'Z' '1'
* flags: one byte
* source size: one integer
* target size: one integer
* instructions follow directly
* ?? no epilogue defined ??
* </pre>
* <p>
* Integers are encoded as a compacted big-endian sequence
* with 7 bits per byte. Leading zero septets are discarded.
* The MSB of each byte is a continue bit which indicates
* another 7 bits are to be read.
* <p>
* <h3>Instruction stream</h3>
* <pre>
* x0000000 - reserved
* 00XXXXXX CAAAAAAA* - copy + 6 bit length + address
* 10XXXXXX CXXXXXXX* CAAAAAAA* - copy + extended length + address
* 010XXXXX DDDDDDDD* - add + 5 bit length - 1 + sequence
* 110XXXXX CXXXXXXX* DDDDDDDD* - add + extended length - 1 + sequence
* 011XXXXX RRRRRRRR - run + 5 bit count - 1 + byte
* 111XXXXX CXXXXXXX* RRRRRRRR - run + extended count - 1 + byte
* </pre>
* <p>
* Opcodes include a length encoded as an integer.
* <dl>
* <dt>COPY
* <dd>The opcode/length followed by an absolute address of the source of
* the copy. COPY is decodeable directly as an integer. A length of 0 is reserved.
* <p>
* The address covers the range of the source buffer concatenated with as much
* of the target buffer as has currently been decoded. The address+length will
* not span buffers.
* <dt>ADD
* <dd>The opcode/length followed by (length+1) bytes of data
* to copy to the current output location.
* <dt>RUN
* <dd>The opcode/length followed by a byte to be duplicated
* into the current output stream (length+1) times.
* </dl>
*
*/
public class DEZEncoder implements ByteDeltaEncoder {
private final ByteArrayOutputStream patch = new ByteArrayOutputStream();
private final byte[] work = new byte[6];
public static final byte[] MAGIC = {'D', 'E', 'Z', '1'};
public static final int COPY = 0x00;
public static final int COPY_EXT = 0x80;
public static final int ADD = 0x40;
public static final int ADD_EXT = 0xc0;
public static final int RUN = 0x60;
public static final int RUN_EXT = 0xe0;
public void init(int sourceSize, int targetSize) {
try {
patch.reset();
patch.write(MAGIC);
// some flags
patch.write(0);
encodeInt(sourceSize);
encodeInt(targetSize);
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Encode an opcode + length.
*
* @param op opcode. extend bit is added automatically.
* @param max maximum size of value that can fit in the first byte inclusive. Leave room for opcode bits.
* @param len length to encode.
*/
private void encodeOp(int op, int max, int len) {
if (len <= max) {
patch.write((byte) (len | op));
} else {
int i = work.length;
int cont = 0;
while (len > max) {
work[--i] = (byte) ((len & 0x7f) | cont);
len >>= 7;
cont = 0x80;
}
work[--i] = (byte) (len | 0x80 | op);
patch.write(work, i, work.length - i);
}
}
/**
* Encodes an integer.
* <p>
* Format is big-endian order encoded as:
* <p>
* CXXXXXXX
* <p>
* Where C is the continue bit.
*
*/
void encodeInt(int addr) {
int i = work.length;
int cont = 0;
while (addr > 0x7f) {
work[--i] = (byte) ((addr & 0x7f) | cont);
addr >>= 7;
cont = 0x80;
}
work[--i] = (byte) (addr | cont);
patch.write(work, i, work.length - i);
}
public void copy(int addr, int len) {
encodeOp(COPY, 0x3f, len);
encodeInt(addr);
}
public void add(byte[] data, int off, int len) {
encodeOp(ADD, 0x1f, len - 1);
patch.write(data, off, len);
}
public void run(byte b, int len) {
encodeOp(RUN, 0x1f, len - 1);
patch.write(b);
}
public byte[] toPatch() {
return patch.toByteArray();
}
}