Commit fde2f4c6 authored by Shu's avatar Shu 🍠
Browse files

Slow start chunk stream naive congestion

parent 53968b61
......@@ -36,6 +36,7 @@
- [~] Entities
- [ ] Collide
- [ ] Get models
- [ ] Reduce compile unit count
- [ ] Review documentation
## Hello universe
......@@ -77,7 +78,8 @@
- [ ] Break area part to entity
- [ ] Slash screen
- [ ] Start/Pause menu
- [ ] QUIC protocal
- [ ] QUIC protocol
- [ ] 8 for 1 contouring problem
- [ ] Use in memory protocol (to replace server_handle)
- [ ] Octree
- [ ] Better Lod
......
......@@ -18,7 +18,7 @@ struct state {
std::optional<uint32_t> color;
};
struct {
std::array<char, 256> buffer;
std::array<char, 256> buffer = {'\0'};
std::vector<line> lines;
} console;
};
......
......@@ -42,9 +42,10 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
++it_c;
}
}
if (chunkChangeArea) { // Request missing chunks
if ((chunkChangeArea && (std::rand() & (1 << 4)-1) == 0) || mayQueryChunks) { // Request missing chunks
ZoneScopedN("Missing");
std::vector<chunk_pos> missing;
std::vector<long> missingDist;
//TODO: use easy sphere fill
const int queryDistance = std::min(loadDistance, serverDistance);
for (int x = -queryDistance; x <= queryDistance; x++) {
......@@ -56,7 +57,23 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
if (chunks.find(p) != chunks.end()) {
contouring->onNotify(std::make_pair(area.first, p), diff, chunks);
} else {
missing.push_back(p);
if (missingDist.size() >= net::MAX_PENDING_CHUNK_COUNT) {
if (dist2 > missingDist.front())
continue;
missingDist.erase(missingDist.begin());
missing.erase(missing.begin());
}
auto it = missingDist.begin();
auto itv = missing.begin();
while (it != missingDist.end()) {
if (dist2 > *it)
break;
++it;
++itv;
}
missingDist.insert(it, dist2);
missing.insert(itv, p);
}
}
}}}
......@@ -67,6 +84,7 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
packet.write(missing.data(), missing.size() * sizeof(chunk_pos));
peer.send(packet.get(), net::channel_type::RELIABLE);
}
mayQueryChunks = false;
}
}
}
......@@ -93,6 +111,7 @@ void DistantUniverse::pullNetwork(voxel_pos pos) {
dict.emplace(packet->data + sizeof(server_packet_type), packet->dataLength - sizeof(server_packet_type));
LOG_T("Compression dictionnary loaded");
mayQueryChunks = true;
break;
}
......@@ -134,6 +153,11 @@ void DistantUniverse::pullNetwork(voxel_pos pos) {
break;
auto reader = PacketReader(packet, true);
if (reader.isFull()) {
mayQueryChunks = true;
break;
}
area_<chunk_pos> pos;
if(!reader.read(pos))
break;
......
......@@ -48,6 +48,7 @@ namespace world::client {
std::optional<zstd::read_dict_ctx> dict;
chunk_pos last_chunk = chunk_pos(INT_MAX);
bool mayQueryChunks = false;
ushort loadDistance;
ushort keepDistance;
......
......@@ -57,6 +57,7 @@ public:
break;
}
}
#if TRACY_ENABLE
TracyPlot("SrvNetUpData", (int64_t)host->totalSentData);
host->totalSentData = 0;
TracyPlot("SrvNetUpPackets", (int64_t)host->totalSentPackets);
......@@ -65,6 +66,16 @@ public:
host->totalReceivedData = 0;
TracyPlot("SrvNetDownPackets", (int64_t)host->totalReceivedPackets);
host->totalReceivedPackets = 0;
int64_t throttling = 0;
int64_t peerCount = 0;
iterPeers([&](peer_t *peer) {
throttling += ENET_PEER_PACKET_THROTTLE_SCALE - peer->packetThrottle;
peerCount++;
});
TracyPlot("SrvPeersCount", peerCount);
TracyPlot("SrvPeersThrottling", peerCount > 0 ? throttling * 100 / ENET_PEER_PACKET_THROTTLE_SCALE / peerCount : 0);
#endif
}
void disconnect(peer_t *peer, disconnect_reason reason) const {
......@@ -74,6 +85,17 @@ public:
template<typename D>
static D* GetPeerData(peer_t* peer) { return (D*)peer->data; }
template<typename Call>
void iterPeers(Call call) {
for (auto currentPeer = host->peers;
currentPeer < &host->peers[host->peerCount];
++currentPeer)
{
if (currentPeer->state == ENET_PEER_STATE_CONNECTED)
call(currentPeer);
}
}
/// Send to single peer
/// Expect salt_t at peer->data
bool sendTo(peer_t* peer, server_packet_type type, const void *data, size_t size, channel_type channel, std::optional<enet_uint32> flags = {}) {
......
......@@ -38,10 +38,11 @@ enum class server_packet_type: enet_uint8 {
AREAS = 16,
/// Full chunk update
/// {area_<chunk_pos>, zstd<chunk rle>} reliable
/// empty: all sent reliable
CHUNK = 17,
/// Chunk changes
/// {area_id, {chunk_pos, ushort(count), Chunk::Edit[]}[]} notify
/// FIXME: to big !!! MAYBE: compress
/// MAYBE: compress
EDITS = 18,
/// Declare entities types
......@@ -70,7 +71,7 @@ enum class client_packet_type: enet_uint8 {
FILL_SHAPE = 0,
/// Request missing chunks
/// area_id, chunk_pos[] reliable
/// area_id, chunk_pos[max: MAX_PENDING_CHUNK_COUNT] reliable
MISSING_CHUNKS = 8,
/// Send public text message
......@@ -82,6 +83,8 @@ enum class client_packet_type: enet_uint8 {
MOVE = 16,
};
constexpr auto MAX_PENDING_CHUNK_COUNT = 256;
struct connection {
std::string address;
int port;
......
......@@ -119,6 +119,46 @@ Universe::~Universe() {
LOG_D("Universe disappeared");
}
struct net_client {
net_client(net::salt_t salt, data::generational::id id): salt(salt), instanceId(id) { }
net::salt_t salt;
data::generational::id instanceId;
std::vector<std::pair<area_<chunk_pos>, long>> pendingChunks;
uint32_t chunkEmitRate = 0;
uint32_t chunkRateEpoch = 0;
bool popPendingChunk(area_<chunk_pos> &out) {
if (pendingChunks.empty())
return false;
out = pendingChunks.back().first;
pendingChunks.pop_back();
return true;
}
void pushChunk(const area_<chunk_pos>& in, long dist) {
for (auto it = pendingChunks.begin(); it != pendingChunks.end(); ++it) {
if (it->first == in) {
pendingChunks.erase(it);
break;
}
}
if (pendingChunks.size() >= net::MAX_PENDING_CHUNK_COUNT) {
if (dist > pendingChunks.front().second)
return;
pendingChunks.erase(pendingChunks.begin());
}
auto it = pendingChunks.begin();
while (it != pendingChunks.end()) {
if (dist > it->second)
break;
++it;
}
pendingChunks.insert(it, std::make_pair(in, dist));
}
};
void Universe::saveAll(bool remove) {
for(auto& area: areas) {
auto& chunks = area.second->setChunks();
......@@ -371,19 +411,54 @@ void Universe::update(float deltaTime) {
for (auto handle = loadedQueue.extractor(); handle.first(loaded);) {
if (const auto it = areas.find(loaded.first.first); it != areas.end()) {
it->second->setChunks().emplace(loaded.first.second, loaded.second);
loadChunk(loaded.first, glm::divide(it->second->getOffset().as_voxel()), it->second->getChunks());
// MAYBE: limit chunks per update
host.broadcast(serializeChunk(loaded), net::channel_type::RELIABLE);
const auto areaOffset = glm::divide(it->second->getOffset().as_voxel());
loadChunk(loaded.first, areaOffset, it->second->getChunks());
host.iterPeers([&](net::peer_t *peer) {
if (peer->data == nullptr)
return;
auto data = net::Server::GetPeerData<net_client>(peer);
if(auto entity = findEntity(PLAYER_ENTITY_ID, data->instanceId)) {
const auto dist = glm::length2(glm::divide(entity->pos.as_voxel()) - areaOffset - loaded.first.second);
if(dist <= glm::pow2(loadDistance))
data->pushChunk(loaded.first, dist);
}
});
}
}
}
}
host.iterPeers([&](net::peer_t *peer) {
if (peer->data == nullptr)
return;
auto data = net::Server::GetPeerData<net_client>(peer);
if (data->pendingChunks.empty())
return;
if (peer->packetThrottle > 3 * ENET_PEER_PACKET_THROTTLE_SCALE / 4)
data->chunkEmitRate = std::min(UINT32_MAX / 2, data->chunkEmitRate + 1 + (data->chunkEmitRate >> 6));
else if (peer->packetThrottleEpoch != data->chunkRateEpoch) {
data->chunkRateEpoch = peer->packetThrottleEpoch;
data->chunkEmitRate /= 2;
}
struct net_client {
net_client(net::salt_t salt, data::generational::id id): salt(salt), instanceId(id) { }
net::salt_t salt;
data::generational::id instanceId;
};
area_<chunk_pos> pending;
size_t i = 0;
while(peer->outgoingDataTotal < data->chunkEmitRate && data->popPendingChunk(pending)) {
if (const auto it = areas.find(pending.first); it != areas.end()) {
if (const auto chunk = it->second->getChunks().findInRange(pending.second)) {
host.send(peer, serializeChunk(pending, std::dynamic_pointer_cast<Chunk>(chunk.value())), net::channel_type::RELIABLE);
i++;
}
}
}
peer->incomingBandwidth = 0;
peer->outgoingDataTotal = 0;
if (data->pendingChunks.empty())
host.send(peer, net::server_packet_type::CHUNK, nullptr, 0, net::channel_type::RELIABLE);
});
}
void Universe::pullNetwork() {
ZoneScopedN("Network");
......@@ -473,20 +548,21 @@ void Universe::pullNetwork() {
break;
}
case client_packet_type::MISSING_CHUNKS: {
if (auto player = findEntity(PLAYER_ENTITY_ID,Server::GetPeerData<net_client>(peer)->instanceId )) {
auto data = Server::GetPeerData<net_client>(peer);
if (auto player = findEntity(PLAYER_ENTITY_ID, data->instanceId )) {
const auto pos = player->pos.as_voxel();
auto reader = PacketReader(packet);
area_id id = *reader.read<area_id>();
if(auto area = areas.find(id); area != areas.end()) {
auto &chunks = area->second->getChunks();
const chunk_pos diff = glm::divide(pos - area->second->getOffset().as_voxel());
while(!reader.isFull()) {
chunk_pos cpos = *reader.read<chunk_pos>();
if(glm::length2(diff - cpos) <= glm::pow2(loadDistance) && chunks.inRange(cpos)) {
if(auto chunk = chunks.find(cpos); chunk != chunks.end()) {
host.send(peer, serializeChunk({std::make_pair(id, cpos), std::dynamic_pointer_cast<Chunk>(chunk->second)}), net::channel_type::RELIABLE);
}
const chunk_pos areaOffset = glm::divide(pos - area->second->getOffset().as_voxel());
for (size_t i = 0; !reader.isFull() && i < MAX_PENDING_CHUNK_COUNT; i++) {
const chunk_pos cpos = *reader.read<chunk_pos>();
const auto dist = glm::length2(areaOffset - cpos);
if (dist <= glm::pow2(loadDistance) && chunks.inRange(cpos) && chunks.findInRange(cpos).has_value()) {
data->pushChunk(std::make_pair(id, cpos), dist);
} else {
LOG_T("Request out of range chunk");
}
......@@ -516,13 +592,14 @@ void Universe::broadcastAreas() {
assert(packet.isFull());
host.broadcast(packet.get(), net::channel_type::RELIABLE);
}
net::packet_t* Universe::serializeChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &pair) {
net::packet_t* Universe::serializeChunk(area_<chunk_pos> id, const std::shared_ptr<Chunk> &data) {
ZoneScopedN("Chunk");
std::ostringstream out;
pair.second->write(out);
data->write(out);
std::vector<char> buffer;
dict_write_ctx.compress(out.str(), buffer);
auto packet = net::Server::makePacket(net::server_packet_type::CHUNK, NULL, sizeof(pair.first) + buffer.size(), ENET_PACKET_FLAG_RELIABLE);
packet.write(pair.first);
auto packet = net::Server::makePacket(net::server_packet_type::CHUNK, NULL, sizeof(id) + buffer.size(), ENET_PACKET_FLAG_RELIABLE);
packet.write(id);
packet.write(buffer.data(), buffer.size());
assert(packet.isFull());
return packet.get();
......
......@@ -69,7 +69,7 @@ namespace world::server {
/// Handle networking requests
void pullNetwork();
void broadcastAreas();
net::packet_t* serializeChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &);
net::packet_t* serializeChunk(area_<chunk_pos>, const std::shared_ptr<Chunk>&);
virtual void broadcastMessage(const std::string &);
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment