Mode GetMode() const noexcept { return mode; }
/// according to current mode, drop this many unimportant packets
unsigned int SuggestedPacketSkip() const noexcept { return (1 << mode) - 1; }
+ /// according to current mode, pause between large uncritical packets for this many ticks
+ unsigned int SuggestedPacketHold() const noexcept { return (1 << (mode + 1)) - 1; }
/// packet loss as factor
float PacketLoss() const noexcept { return packet_loss; }
void UpdateRTT(std::uint16_t) noexcept;
bool SamplePacket(std::uint16_t) const noexcept;
+ std::size_t SampleIndex(std::uint16_t) const noexcept;
void UpdateStats() noexcept;
float packet_loss;
Uint32 stamps[16];
- std::size_t stamp_cursor;
std::uint16_t stamp_last;
float rtt;
, packets_lost(0)
, packets_received(0)
, packet_loss(0.0f)
-, stamp_cursor(15)
, stamp_last(0)
, rtt(64.0f)
, next_sample(1000)
, tx_kbps(0.0f)
, rx_kbps(0.0f)
, mode(GOOD)
-// rtt > 100ms or packet loss > 5% is BAD
-, bad_rtt(100.0f)
+// rtt > 75ms or packet loss > 5% is BAD
+, bad_rtt(75.0f)
, bad_loss(0.05f)
-// rtt > 250ms or packet loss > 15% is UGLY
-, ugly_rtt(250.0f)
+// rtt > 150ms or packet loss > 15% is UGLY
+, ugly_rtt(150.0f)
, ugly_loss(0.15f)
, mode_keep_time(1000) {
Uint32 now = SDL_GetTicks();
if (!SamplePacket(seq)) {
return;
}
- stamp_cursor = (stamp_cursor + 1) % 16;
- stamps[stamp_cursor] = SDL_GetTicks();
+ stamps[SampleIndex(seq)] = SDL_GetTicks();
stamp_last = seq;
}
}
}
-void CongestionControl::UpdateRTT(std::uint16_t seq) noexcept {
+void CongestionControl::UpdateRTT(uint16_t seq) noexcept {
if (!SamplePacket(seq)) return;
- int16_t diff = int16_t(seq) - int16_t(stamp_last);
- diff /= sample_skip;
- if (diff > 0 || diff < -15) {
- // packet outside observed time frame
+ int16_t diff = int16_t(stamp_last) - int16_t(seq);
+ if (diff < 0 || diff > int(15 * sample_skip)) {
+ // packet outside observed frame
return;
}
- int cur_rtt = SDL_GetTicks() - stamps[(stamp_cursor + diff + 16) % 16];
+ int cur_rtt = SDL_GetTicks() - stamps[SampleIndex(seq)];
rtt += (cur_rtt - rtt) * 0.1f;
}
-bool CongestionControl::SamplePacket(std::uint16_t seq) const noexcept {
+bool CongestionControl::SamplePacket(uint16_t seq) const noexcept {
return seq % sample_skip == 0;
}
+size_t CongestionControl::SampleIndex(uint16_t seq) const noexcept {
+ return (seq / sample_skip) % 16;
+}
+
void CongestionControl::PacketIn(const UDPpacket &pack) noexcept {
rx_bytes += pack.len + packet_overhead;
UpdateStats();
Connection::Connection(const IPaddress &addr)
: handler(nullptr)
, addr(addr)
-, send_timer(500)
+// make sure a packet is sent at least every 50ms since packets contains
+// acks that the remote end will use to measure RTT
+, send_timer(50)
, recv_timer(10000)
, ctrl_out{ 0, 0xFFFF, 0xFFFFFFFF }
, ctrl_in{ 0, 0xFFFF, 0xFFFFFFFF }
}
/// send the previously prepared packet
std::uint16_t Send();
- /// send the previously prepared packet of non-default length
+ /// send the previously prepared packet of given payload length
std::uint16_t Send(std::size_t len);
void AttachPlayer(Player &);
void On(const Packet::PlayerUpdate &) override;
void On(const Packet::Message &) override;
+ void CheckEntities();
bool CanSpawn(const Entity &) const noexcept;
bool CanDespawn(const Entity &) const noexcept;
void SendSpawn(SpawnStatus &);
void SendDespawn(SpawnStatus &);
- /// true if updates are pushed to the client this frame
+ /// true if entity updates are pushed to the client this frame
bool SendingUpdates() const noexcept;
void QueueUpdate(SpawnStatus &);
void SendUpdates();
ChunkTransmitter transmitter;
std::deque<glm::ivec3> chunk_queue;
glm::ivec3 old_base;
+ unsigned int chunk_blocks_skipped;
};
if (data_packets[i] == -1) {
++confirm_wait;
}
- data_packets[i] = conn.Send();
+ data_packets[i] = conn.Send(Packet::ChunkData::GetSize(len));
}
void ChunkTransmitter::Release() {
, old_actions(0)
, transmitter(*this)
, chunk_queue()
-, old_base() {
+, old_base()
+, chunk_blocks_skipped(0) {
conn.SetHandler(this);
}
return;
}
if (HasPlayer()) {
- // sync entities
- auto global_iter = server.GetWorld().Entities().begin();
- auto global_end = server.GetWorld().Entities().end();
- auto local_iter = spawns.begin();
- auto local_end = spawns.end();
-
- while (global_iter != global_end && local_iter != local_end) {
- if (global_iter->ID() == local_iter->entity->ID()) {
- // they're the same
- if (CanDespawn(*global_iter)) {
- SendDespawn(*local_iter);
- } else if (SendingUpdates()) {
- // update
- QueueUpdate(*local_iter);
- }
- ++global_iter;
- ++local_iter;
- } else if (global_iter->ID() < local_iter->entity->ID()) {
- // global entity was inserted
- if (CanSpawn(*global_iter)) {
- auto spawned = spawns.emplace(local_iter, *global_iter);
- SendSpawn(*spawned);
- }
- ++global_iter;
- } else {
- // global entity was removed
+ CheckPlayerFix();
+ CheckChunkQueue();
+ CheckEntities();
+ SendUpdates();
+ }
+ if (conn.ShouldPing()) {
+ conn.SendPing(server.GetPacket(), server.GetSocket());
+ }
+}
+
+void ClientConnection::CheckEntities() {
+ auto global_iter = server.GetWorld().Entities().begin();
+ auto global_end = server.GetWorld().Entities().end();
+ auto local_iter = spawns.begin();
+ auto local_end = spawns.end();
+
+ while (global_iter != global_end && local_iter != local_end) {
+ if (global_iter->ID() == local_iter->entity->ID()) {
+ // they're the same
+ if (CanDespawn(*global_iter)) {
SendDespawn(*local_iter);
- ++local_iter;
+ } else if (SendingUpdates()) {
+ // update
+ QueueUpdate(*local_iter);
}
- }
-
- // leftover spawns
- while (global_iter != global_end) {
+ ++global_iter;
+ ++local_iter;
+ } else if (global_iter->ID() < local_iter->entity->ID()) {
+ // global entity was inserted
if (CanSpawn(*global_iter)) {
- spawns.emplace_back(*global_iter);
- SendSpawn(spawns.back());
+ auto spawned = spawns.emplace(local_iter, *global_iter);
+ SendSpawn(*spawned);
}
++global_iter;
- }
-
- // leftover despawns
- while (local_iter != local_end) {
+ } else {
+ // global entity was removed
SendDespawn(*local_iter);
++local_iter;
}
- SendUpdates();
+ }
- CheckPlayerFix();
- CheckChunkQueue();
+ // leftover spawns
+ while (global_iter != global_end) {
+ if (CanSpawn(*global_iter)) {
+ spawns.emplace_back(*global_iter);
+ SendSpawn(spawns.back());
+ }
+ ++global_iter;
}
- if (conn.ShouldPing()) {
- conn.SendPing(server.GetPacket(), server.GetSocket());
+
+ // leftover despawns
+ while (local_iter != local_end) {
+ SendDespawn(*local_iter);
+ ++local_iter;
}
}
}
bool ClientConnection::SendingUpdates() const noexcept {
- return entity_updates_skipped >= NetStat().SuggestedPacketSkip();
+ return entity_updates_skipped >= NetStat().SuggestedPacketHold();
}
void ClientConnection::QueueUpdate(SpawnStatus &status) {
old_base = PlayerChunks().Base();
sort(chunk_queue.begin(), chunk_queue.end(), QueueCompare(old_base));
}
- // if we have packet skip enabled and just pushed an entity
- // update, don't also send chunk data
- if (NetStat().SuggestedPacketSkip() > 0 && entity_updates_skipped == 0) {
+ // don't push entity updates and chunk data in the same tick
+ if (chunk_blocks_skipped >= NetStat().SuggestedPacketHold() && !SendingUpdates()) {
+ ++chunk_blocks_skipped;
return;
}
if (transmitter.Transmitting()) {
transmitter.Transmit();
+ chunk_blocks_skipped = 0;
return;
}
if (transmitter.Idle()) {
Chunk *chunk = PlayerChunks().Get(pos);
if (chunk) {
transmitter.Send(*chunk);
+ chunk_blocks_skipped = 0;
return;
} else {
chunk_queue.push_back(pos);