mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-01-16 14:21:43 -08:00
First client/server snapshot implementation
This commit is contained in:
parent
0bd0602655
commit
8e00bdcf30
7 changed files with 132 additions and 25 deletions
|
|
@ -1,8 +1,12 @@
|
|||
package io.anuke.mindustry.core;
|
||||
|
||||
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.entities.Player;
|
||||
import io.anuke.mindustry.entities.traits.SyncTrait;
|
||||
import io.anuke.mindustry.gen.Call;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
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.ucore.core.Timers;
|
||||
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.util.Log;
|
||||
import io.anuke.ucore.util.Timer;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class NetClient extends Module {
|
||||
|
|
@ -27,6 +38,15 @@ public class NetClient extends Module {
|
|||
private boolean quiet = false;
|
||||
/**Counter for data timeout.*/
|
||||
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(){
|
||||
|
||||
|
|
@ -135,6 +155,7 @@ public class NetClient extends Module {
|
|||
Player player = players[0];
|
||||
|
||||
ClientSnapshotPacket packet = new ClientSnapshotPacket();
|
||||
packet.lastSnapshot = lastSnapshotID;
|
||||
packet.player = player;
|
||||
Net.send(packet, SendMode.udp);
|
||||
}
|
||||
|
|
@ -143,4 +164,73 @@ public class NetClient extends Module {
|
|||
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.modules.Module;
|
||||
import io.anuke.ucore.util.Log;
|
||||
import io.anuke.ucore.util.Timer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
|
@ -43,7 +42,6 @@ public class NetServer extends Module{
|
|||
/**Maps connection IDs to players.*/
|
||||
private IntMap<Player> connections = new IntMap<>();
|
||||
private boolean closing = false;
|
||||
private Timer timer = new Timer(5);
|
||||
|
||||
/**Stream for writing player sync data to.*/
|
||||
private ByteArrayOutputStream syncStream = new ByteArrayOutputStream();
|
||||
|
|
@ -131,8 +129,8 @@ public class NetServer extends Module{
|
|||
Platform.instance.updateRPC();
|
||||
});
|
||||
|
||||
|
||||
Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {});
|
||||
//update last recieved snapshot based on client snapshot
|
||||
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)));
|
||||
}
|
||||
|
|
@ -190,6 +188,19 @@ public class NetServer extends Module{
|
|||
|
||||
//iterate through each player
|
||||
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
|
||||
syncStream.reset();
|
||||
|
||||
|
|
@ -207,24 +218,32 @@ public class NetServer extends Module{
|
|||
//TODO range-check sync positions?
|
||||
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
|
||||
dataStream.writeByte(group.getID());
|
||||
dataStream.writeShort(group.size());
|
||||
//write timestamp
|
||||
dataStream.writeLong(TimeUtils.millis());
|
||||
|
||||
for(Entity entity : group.all()){
|
||||
//write all entities now
|
||||
dataStream.writeInt(entity.getID());
|
||||
((SyncTrait)entity).write(dataStream);
|
||||
}
|
||||
}
|
||||
|
||||
NetConnection connection = Net.getConnection(player.clientid);
|
||||
|
||||
byte[] bytes = syncStream.toByteArray();
|
||||
if(connection.lastSnapshot == null){
|
||||
//no snapshot to diff, send it all
|
||||
Call.onSnapshot(connection.id, bytes, -1);
|
||||
}else{
|
||||
//send diff otherwise
|
||||
//increment snapshot ID
|
||||
connection.lastSnapshotID ++;
|
||||
//send diff, otherwise
|
||||
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder);
|
||||
Call.onSnapshot(connection.id, diff, connection.lastSnapshotID);
|
||||
|
||||
|
|
@ -240,7 +259,7 @@ public class NetServer extends Module{
|
|||
@Remote(server = false)
|
||||
public static void connectConfirm(Player player){
|
||||
player.add();
|
||||
Log.info("&y{0} has connected.", player.name);
|
||||
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 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
|
||||
|
||||
public float baseRotation;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ public abstract class NetConnection {
|
|||
public final String address;
|
||||
|
||||
/**ID of last snapshot this connection is guaranteed to have recieved.*/
|
||||
public int lastSnapshotID;
|
||||
/**Byte array of last sent snapshot data that is confirmed to be recieved..*/
|
||||
public int lastSnapshotID = -1;
|
||||
/**Byte array of last sent snapshot data that is confirmed to be recieved.*/
|
||||
public byte[] lastSnapshot;
|
||||
|
||||
/**ID of last sent snapshot.*/
|
||||
public int lastSentSnapshotID = -1;
|
||||
/**Byte array of last sent snapshot.*/
|
||||
public byte[] lastSentSnapshot;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,5 @@
|
|||
package io.anuke.mindustry.net;
|
||||
|
||||
import io.anuke.annotations.Annotations.Remote;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
|
||||
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 Player player;
|
||||
public int lastSnapshot;
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
ByteBufferOutput out = new ByteBufferOutput(buffer);
|
||||
|
||||
buffer.putInt(lastSnapshot);
|
||||
buffer.putInt(player.id);
|
||||
buffer.putLong(TimeUtils.millis());
|
||||
player.write(out);
|
||||
|
|
@ -114,6 +116,7 @@ public class Packets {
|
|||
public void read(ByteBuffer buffer) {
|
||||
ByteBufferInput in = new ByteBufferInput(buffer);
|
||||
|
||||
lastSnapshot = buffer.getInt();
|
||||
int id = buffer.getInt();
|
||||
long time = buffer.getLong();
|
||||
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){
|
||||
int t = left ? 1 : 2;
|
||||
int t2 = !left ? 1 : 2;
|
||||
int t = left ? Player.timerShootLeft : Player.timerShootRight;
|
||||
int t2 = !left ? Player.timerShootRight : Player.timerShootLeft;
|
||||
if(p.inventory.hasAmmo() && p.timer.get(t, reload)){
|
||||
if(roundrobin){
|
||||
p.timer.reset(t2, reload/2f);
|
||||
|
|
@ -70,7 +70,7 @@ public class Weapon extends Upgrade {
|
|||
}
|
||||
|
||||
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(){
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue