detect end of map transfer with state packet

Previously, the end of map transfer was detected by a number of fallible
heuristics, including checking if the map is loadable on every non-map
packet, when a timeout had been reached, or the map size given by the
server was reached. This broke in a number of scenarios, including when
an unexpected packet was sent.

Instead, we now detect the end of the map transfer when we receive a
state packet, as intended by the protocol.
This commit is contained in:
NotAFile 2019-05-04 18:51:46 +02:00
parent 5ad2cf6ab0
commit 2ecd3ced44
2 changed files with 22 additions and 95 deletions

View File

@ -315,8 +315,6 @@ namespace spades {
PlayerInput actualInput = player->GetInput();
WeaponInput actualWeapInput = player->GetWeaponInput();
Vector3 vel = player->GetVelocty();
vel.z = 0.f;
if (actualInput.sprint && player->IsAlive()) {
sprintState += dt * 4.f;
if (sprintState > 1.f)

View File

@ -424,7 +424,6 @@ namespace spades {
status = NetClientStatusConnecting;
statusString = _Tr("NetClient", "Connecting to the server");
timeToTryMapLoad = 0;
}
void NetClient::Disconnect() {
@ -529,8 +528,6 @@ namespace spades {
mapSize = reader.ReadInt();
status = NetClientStatusReceivingMap;
statusString = _Tr("NetClient", "Loading snapshot");
timeToTryMapLoad = 30;
tryMapLoadOnPacketType = true;
}
} else if (status == NetClientStatusReceivingMap) {
if (event.type == ENET_EVENT_TYPE_RECEIVE) {
@ -541,60 +538,29 @@ namespace spades {
dt.erase(dt.begin());
mapData.insert(mapData.end(), dt.begin(), dt.end());
timeToTryMapLoad = 200;
statusString = _Tr("NetClient", "Loading snapshot ({0}/{1})",
mapData.size(), mapSize);
if (mapSize == mapData.size()) {
status = NetClientStatusConnected;
statusString = _Tr("NetClient", "Connected");
try {
MapLoaded();
} catch (const std::exception &ex) {
if (strstr(ex.what(), "File truncated") ||
strstr(ex.what(), "EOF reached")) {
SPLog("Map decoder returned error. Maybe we will get more "
"data...:\n%s",
ex.what());
// hack: more data to load...
status = NetClientStatusReceivingMap;
statusString = _Tr("NetClient", "Still loading...");
} else {
Disconnect();
statusString = _Tr("NetClient", "Error");
throw;
}
} catch (...) {
Disconnect();
statusString = _Tr("NetClient", "Error");
throw;
}
}
} else {
reader.DumpDebug();
// On pyspades and derivative servers the actual size of the map data
// cannot be known in beforehand, so we have to find the end of the data
// by one of other means. One indicator for this would be a packet of a
// type other than MapChunk, which usually marks the end of map data
// transfer.
// The actual size of the map data cannot be known beforehand because
// of compression. This means we must detect the end of the map
// transfer in another way.
//
// However, we can't rely on this heuristics entirely because there are
// several occasions where the server would send non-MapChunk packets
// during map loading sequence, for example:
// We do this by checking for a StateData packet, which is sent
// directly after the map transfer completes.
//
// A number of other packets can also be received while loading the map:
//
// - World update packets (WorldUpdate, ExistingPlayer, and
// CreatePlayer) for the current round. We must store such packets
// temporarily and process them later when a `World` is created.
// temporarily and process them later when a `World` is created.
//
// - Leftover reload packet from the previous round. This happens when
// you initiate the reload action and a map change occurs before it
// is completed. In pyspades, sending a reload packet is implemented
// by registering a callback function to the Twisted reactor. This
// is completed. In pyspades, sending a reload packet is implemented
// by registering a callback function to the Twisted reactor. This
// callback function sends a reload packet, but it does not check if
// the current game round is finished, nor is it unregistered on a
// map change.
@ -604,29 +570,18 @@ namespace spades {
// an "invalid player ID" exception, so we simply drop it during
// map load sequence.
//
if (reader.GetType() == PacketTypeWeaponReload) {
// Drop reload packets
} else if (reader.GetType() != PacketTypeWorldUpdate &&
reader.GetType() != PacketTypeExistingPlayer &&
reader.GetType() != PacketTypeCreatePlayer &&
tryMapLoadOnPacketType) {
if (reader.GetType() == PacketTypeStateData) {
status = NetClientStatusConnected;
statusString = _Tr("NetClient", "Connected");
try {
MapLoaded();
} catch (const std::exception &ex) {
tryMapLoadOnPacketType = false;
if (strstr(ex.what(), "File truncated") ||
strstr(ex.what(), "EOF reached")) {
SPLog("Map decoder returned error. Maybe we will get more "
"data...:\n%s",
SPLog("Map decoder returned error:\n%s",
ex.what());
// hack: more data to load...
status = NetClientStatusReceivingMap;
statusString = _Tr("NetClient", "Still loading...");
goto stillLoading;
} else {
Disconnect();
statusString = _Tr("NetClient", "Error");
throw;
@ -637,12 +592,17 @@ namespace spades {
throw;
}
HandleGamePacket(reader);
} else {
stillLoading:
} else if (reader.GetType() == PacketTypeVersionGet) {
// Packets that should be handled now even when
// the map is downloading
HandleGamePacket(reader);
} else if (reader.GetType() != PacketTypeWeaponReload) {
// Drop the reload packet. Pyspades does not
// cancel the reload packets on map change and
// they would cause an error if we would
// process them
savedPackets.push_back(reader.GetData());
}
// HandleGamePacket(reader);
}
}
} else if (status == NetClientStatusConnected) {
@ -660,37 +620,6 @@ namespace spades {
}
}
}
if (status == NetClientStatusReceivingMap) {
if (timeToTryMapLoad > 0) {
timeToTryMapLoad--;
if (timeToTryMapLoad == 0) {
try {
MapLoaded();
} catch (const std::exception &ex) {
if ((strstr(ex.what(), "File truncated") ||
strstr(ex.what(), "EOF reached")) &&
savedPackets.size() < 400) {
// hack: more data to load...
SPLog(
"Map decoder returned error. Maybe we will get more data...:\n%s",
ex.what());
status = NetClientStatusReceivingMap;
statusString = _Tr("NetClient", "Still loading...");
timeToTryMapLoad = 200;
} else {
Disconnect();
statusString = _Tr("NetClient", "Error");
throw;
}
} catch (...) {
Disconnect();
statusString = _Tr("NetClient", "Error");
throw;
}
}
}
}
}
World *NetClient::GetWorld() { return client->GetWorld(); }