First client/server snapshot implementation
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
package io.anuke.mindustry.core;
|
package io.anuke.mindustry.core;
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.utils.reflect.ClassReflection;
|
||||||
|
import com.badlogic.gdx.utils.reflect.ReflectionException;
|
||||||
|
import io.anuke.annotations.Annotations.Remote;
|
||||||
import io.anuke.mindustry.core.GameState.State;
|
import io.anuke.mindustry.core.GameState.State;
|
||||||
import io.anuke.mindustry.entities.Player;
|
import io.anuke.mindustry.entities.Player;
|
||||||
|
import io.anuke.mindustry.entities.traits.SyncTrait;
|
||||||
import io.anuke.mindustry.gen.Call;
|
import io.anuke.mindustry.gen.Call;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
import io.anuke.mindustry.net.Net.SendMode;
|
import io.anuke.mindustry.net.Net.SendMode;
|
||||||
@@ -10,10 +14,17 @@ import io.anuke.mindustry.net.NetworkIO;
|
|||||||
import io.anuke.mindustry.net.Packets.*;
|
import io.anuke.mindustry.net.Packets.*;
|
||||||
import io.anuke.ucore.core.Timers;
|
import io.anuke.ucore.core.Timers;
|
||||||
import io.anuke.ucore.entities.Entities;
|
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.modules.Module;
|
||||||
import io.anuke.ucore.util.Log;
|
import io.anuke.ucore.util.Log;
|
||||||
import io.anuke.ucore.util.Timer;
|
import io.anuke.ucore.util.Timer;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.*;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
public class NetClient extends Module {
|
public class NetClient extends Module {
|
||||||
@@ -27,6 +38,15 @@ public class NetClient extends Module {
|
|||||||
private boolean quiet = false;
|
private boolean quiet = false;
|
||||||
/**Counter for data timeout.*/
|
/**Counter for data timeout.*/
|
||||||
private float timeoutTime = 0f;
|
private float timeoutTime = 0f;
|
||||||
|
/**Last snapshot recieved.*/
|
||||||
|
private byte[] lastSnapshot;
|
||||||
|
/**Last snapshot ID recieved.*/
|
||||||
|
private int lastSnapshotID = -1;
|
||||||
|
/**Decoder for uncompressing snapshots.*/
|
||||||
|
private DEZDecoder decoder = new DEZDecoder();
|
||||||
|
/**Byte stream for reading in snapshots.*/
|
||||||
|
private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream();
|
||||||
|
private DataInputStream dataStream = new DataInputStream(byteStream);
|
||||||
|
|
||||||
public NetClient(){
|
public NetClient(){
|
||||||
|
|
||||||
@@ -135,6 +155,7 @@ public class NetClient extends Module {
|
|||||||
Player player = players[0];
|
Player player = players[0];
|
||||||
|
|
||||||
ClientSnapshotPacket packet = new ClientSnapshotPacket();
|
ClientSnapshotPacket packet = new ClientSnapshotPacket();
|
||||||
|
packet.lastSnapshot = lastSnapshotID;
|
||||||
packet.player = player;
|
packet.player = player;
|
||||||
Net.send(packet, SendMode.udp);
|
Net.send(packet, SendMode.udp);
|
||||||
}
|
}
|
||||||
@@ -143,4 +164,73 @@ public class NetClient extends Module {
|
|||||||
Net.updatePing();
|
Net.updatePing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Remote(one = true, all = false, unreliable = true)
|
||||||
|
public static void onSnapshot(byte[] snapshot, int snapshotID){
|
||||||
|
|
||||||
|
//skip snapshot IDs that have already been recieved
|
||||||
|
if(snapshotID == netClient.lastSnapshotID){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
int length;
|
||||||
|
if (snapshotID == -1) { //-1 = fresh snapshot
|
||||||
|
result = snapshot;
|
||||||
|
length = snapshot.length;
|
||||||
|
netClient.lastSnapshot = snapshot;
|
||||||
|
} else { //otherwise, last snapshot must not be null, decode it
|
||||||
|
netClient.decoder.init(netClient.lastSnapshot, snapshot);
|
||||||
|
result = netClient.decoder.decode();
|
||||||
|
length = netClient.decoder.getDecodedLength();
|
||||||
|
//set last snapshot to a copy to prevent issues
|
||||||
|
netClient.lastSnapshot = Arrays.copyOf(result, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
//set stream bytes to begin write
|
||||||
|
netClient.byteStream.setBytes(result, 0, length);
|
||||||
|
|
||||||
|
//get data input for reading from the stream
|
||||||
|
DataInputStream input = netClient.dataStream;
|
||||||
|
|
||||||
|
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();
|
||||||
|
long timestamp = input.readLong();
|
||||||
|
|
||||||
|
EntityGroup<?> group = Entities.getGroup(groupID);
|
||||||
|
|
||||||
|
//go through each entity
|
||||||
|
for (int j = 0; j < amount; j++) {
|
||||||
|
int id = input.readInt();
|
||||||
|
|
||||||
|
SyncTrait entity = (SyncTrait) group.getByID(id);
|
||||||
|
|
||||||
|
//entity must not be added yet, so create it
|
||||||
|
if(entity == null){
|
||||||
|
entity = (SyncTrait) ClassReflection.newInstance(group.getType()); //TODO solution without reflection?
|
||||||
|
entity.add();
|
||||||
|
}
|
||||||
|
|
||||||
|
//read the entity
|
||||||
|
entity.read(input, timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//confirm that snapshot 0 has been recieved if this is the initial snapshot
|
||||||
|
if(snapshotID == -1){
|
||||||
|
netClient.lastSnapshotID = 0;
|
||||||
|
}else{ //confirm that the snapshot has been recieved
|
||||||
|
netClient.lastSnapshotID = snapshotID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}catch (IOException | ReflectionException e){
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,6 @@ import io.anuke.ucore.io.delta.ByteMatcherHash;
|
|||||||
import io.anuke.ucore.io.delta.DEZEncoder;
|
import io.anuke.ucore.io.delta.DEZEncoder;
|
||||||
import io.anuke.ucore.modules.Module;
|
import io.anuke.ucore.modules.Module;
|
||||||
import io.anuke.ucore.util.Log;
|
import io.anuke.ucore.util.Log;
|
||||||
import io.anuke.ucore.util.Timer;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -43,7 +42,6 @@ public class NetServer extends Module{
|
|||||||
/**Maps connection IDs to players.*/
|
/**Maps connection IDs to players.*/
|
||||||
private IntMap<Player> connections = new IntMap<>();
|
private IntMap<Player> connections = new IntMap<>();
|
||||||
private boolean closing = false;
|
private boolean closing = false;
|
||||||
private Timer timer = new Timer(5);
|
|
||||||
|
|
||||||
/**Stream for writing player sync data to.*/
|
/**Stream for writing player sync data to.*/
|
||||||
private ByteArrayOutputStream syncStream = new ByteArrayOutputStream();
|
private ByteArrayOutputStream syncStream = new ByteArrayOutputStream();
|
||||||
@@ -131,8 +129,8 @@ public class NetServer extends Module{
|
|||||||
Platform.instance.updateRPC();
|
Platform.instance.updateRPC();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//update last recieved snapshot based on client snapshot
|
||||||
Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {});
|
Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> Net.getConnection(id).lastSnapshotID = packet.lastSnapshot);
|
||||||
|
|
||||||
Net.handleServer(InvokePacket.class, (id, packet) -> RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id)));
|
Net.handleServer(InvokePacket.class, (id, packet) -> RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id)));
|
||||||
}
|
}
|
||||||
@@ -190,6 +188,19 @@ public class NetServer extends Module{
|
|||||||
|
|
||||||
//iterate through each player
|
//iterate through each player
|
||||||
for (Player player : connections.values()) {
|
for (Player player : connections.values()) {
|
||||||
|
NetConnection connection = Net.getConnection(player.clientid);
|
||||||
|
|
||||||
|
if(!player.timer.get(Player.timeSync, serverSyncTime)) continue;
|
||||||
|
|
||||||
|
//if the player hasn't acknolwedged that it has recieved the packet, send the same thing again
|
||||||
|
if(connection.lastSentSnapshotID > connection.lastSnapshotID){
|
||||||
|
Call.onSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
//set up last confirmed snapshot to the last one that was sent, otherwise
|
||||||
|
connection.lastSnapshot = connection.lastSentSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
//reset stream to begin writing
|
//reset stream to begin writing
|
||||||
syncStream.reset();
|
syncStream.reset();
|
||||||
|
|
||||||
@@ -207,24 +218,32 @@ public class NetServer extends Module{
|
|||||||
//TODO range-check sync positions?
|
//TODO range-check sync positions?
|
||||||
if (group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue;
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
//write group ID + group size
|
//write group ID + group size
|
||||||
dataStream.writeByte(group.getID());
|
dataStream.writeByte(group.getID());
|
||||||
dataStream.writeShort(group.size());
|
dataStream.writeShort(group.size());
|
||||||
|
//write timestamp
|
||||||
|
dataStream.writeLong(TimeUtils.millis());
|
||||||
|
|
||||||
for(Entity entity : group.all()){
|
for(Entity entity : group.all()){
|
||||||
//write all entities now
|
//write all entities now
|
||||||
|
dataStream.writeInt(entity.getID());
|
||||||
((SyncTrait)entity).write(dataStream);
|
((SyncTrait)entity).write(dataStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetConnection connection = Net.getConnection(player.clientid);
|
|
||||||
|
|
||||||
byte[] bytes = syncStream.toByteArray();
|
byte[] bytes = syncStream.toByteArray();
|
||||||
if(connection.lastSnapshot == null){
|
if(connection.lastSnapshot == null){
|
||||||
//no snapshot to diff, send it all
|
//no snapshot to diff, send it all
|
||||||
Call.onSnapshot(connection.id, bytes, -1);
|
Call.onSnapshot(connection.id, bytes, -1);
|
||||||
}else{
|
}else{
|
||||||
//send diff otherwise
|
//increment snapshot ID
|
||||||
|
connection.lastSnapshotID ++;
|
||||||
|
//send diff, otherwise
|
||||||
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder);
|
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder);
|
||||||
Call.onSnapshot(connection.id, diff, connection.lastSnapshotID);
|
Call.onSnapshot(connection.id, diff, connection.lastSnapshotID);
|
||||||
|
|
||||||
@@ -240,7 +259,7 @@ public class NetServer extends Module{
|
|||||||
@Remote(server = false)
|
@Remote(server = false)
|
||||||
public static void connectConfirm(Player player){
|
public static void connectConfirm(Player player){
|
||||||
player.add();
|
player.add();
|
||||||
Log.info("&y{0} has connected.", player.name);
|
|
||||||
netCommon.sendMessage("[accent]" + player.name + " has connected.");
|
netCommon.sendMessage("[accent]" + player.name + " has connected.");
|
||||||
|
Log.info("&y{0} has connected.", player.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait {
|
|||||||
private static final float dashSpeed = 1.8f;
|
private static final float dashSpeed = 1.8f;
|
||||||
private static final Vector2 movement = new Vector2();
|
private static final Vector2 movement = new Vector2();
|
||||||
|
|
||||||
|
public static final int timerShootLeft = 0;
|
||||||
|
public static final int timerShootRight = 1;
|
||||||
|
public static final int timeSync = 2;
|
||||||
|
|
||||||
//region instance variables, constructor
|
//region instance variables, constructor
|
||||||
|
|
||||||
public float baseRotation;
|
public float baseRotation;
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ public abstract class NetConnection {
|
|||||||
public final String address;
|
public final String address;
|
||||||
|
|
||||||
/**ID of last snapshot this connection is guaranteed to have recieved.*/
|
/**ID of last snapshot this connection is guaranteed to have recieved.*/
|
||||||
public int lastSnapshotID;
|
public int lastSnapshotID = -1;
|
||||||
/**Byte array of last sent snapshot data that is confirmed to be recieved..*/
|
/**Byte array of last sent snapshot data that is confirmed to be recieved.*/
|
||||||
public byte[] lastSnapshot;
|
public byte[] lastSnapshot;
|
||||||
|
|
||||||
|
/**ID of last sent snapshot.*/
|
||||||
|
public int lastSentSnapshotID = -1;
|
||||||
/**Byte array of last sent snapshot.*/
|
/**Byte array of last sent snapshot.*/
|
||||||
public byte[] lastSentSnapshot;
|
public byte[] lastSentSnapshot;
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,5 @@
|
|||||||
package io.anuke.mindustry.net;
|
package io.anuke.mindustry.net;
|
||||||
|
|
||||||
import io.anuke.annotations.Annotations.Remote;
|
|
||||||
import io.anuke.mindustry.entities.Player;
|
|
||||||
|
|
||||||
public class NetEvents {
|
public class NetEvents {
|
||||||
|
|
||||||
@Remote(one = true, all = false, unreliable = true)
|
|
||||||
public static void onSnapshot(byte[] snapshot, int snapshotID){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Remote(unreliable = true, server = false)
|
|
||||||
public static void onRecievedSnapshot(Player player, int snapshotID){
|
|
||||||
Net.getConnection(player.clientid).lastSnapshotID = snapshotID;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,11 +100,13 @@ public class Packets {
|
|||||||
|
|
||||||
public static class ClientSnapshotPacket implements Packet{
|
public static class ClientSnapshotPacket implements Packet{
|
||||||
public Player player;
|
public Player player;
|
||||||
|
public int lastSnapshot;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(ByteBuffer buffer) {
|
public void write(ByteBuffer buffer) {
|
||||||
ByteBufferOutput out = new ByteBufferOutput(buffer);
|
ByteBufferOutput out = new ByteBufferOutput(buffer);
|
||||||
|
|
||||||
|
buffer.putInt(lastSnapshot);
|
||||||
buffer.putInt(player.id);
|
buffer.putInt(player.id);
|
||||||
buffer.putLong(TimeUtils.millis());
|
buffer.putLong(TimeUtils.millis());
|
||||||
player.write(out);
|
player.write(out);
|
||||||
@@ -114,6 +116,7 @@ public class Packets {
|
|||||||
public void read(ByteBuffer buffer) {
|
public void read(ByteBuffer buffer) {
|
||||||
ByteBufferInput in = new ByteBufferInput(buffer);
|
ByteBufferInput in = new ByteBufferInput(buffer);
|
||||||
|
|
||||||
|
lastSnapshot = buffer.getInt();
|
||||||
int id = buffer.getInt();
|
int id = buffer.getInt();
|
||||||
long time = buffer.getLong();
|
long time = buffer.getLong();
|
||||||
player = Vars.playerGroup.getByID(id);
|
player = Vars.playerGroup.getByID(id);
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public class Weapon extends Upgrade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update(Player p, boolean left, float pointerX, float pointerY){
|
public void update(Player p, boolean left, float pointerX, float pointerY){
|
||||||
int t = left ? 1 : 2;
|
int t = left ? Player.timerShootLeft : Player.timerShootRight;
|
||||||
int t2 = !left ? 1 : 2;
|
int t2 = !left ? Player.timerShootRight : Player.timerShootLeft;
|
||||||
if(p.inventory.hasAmmo() && p.timer.get(t, reload)){
|
if(p.inventory.hasAmmo() && p.timer.get(t, reload)){
|
||||||
if(roundrobin){
|
if(roundrobin){
|
||||||
p.timer.reset(t2, reload/2f);
|
p.timer.reset(t2, reload/2f);
|
||||||
@@ -70,7 +70,7 @@ public class Weapon extends Upgrade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public float getRecoil(Player player, boolean left){
|
public float getRecoil(Player player, boolean left){
|
||||||
return 1f-Mathf.clamp(player.timer.getTime(left ? 1 : 2)/reload);
|
return 1f-Mathf.clamp(player.timer.getTime(left ? Player.timerShootLeft : Player.timerShootRight)/reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getReload(){
|
public float getReload(){
|
||||||
|
|||||||
Reference in New Issue
Block a user