1
0
Fork 0

Compare commits

...

1 Commits

Author SHA1 Message Date
May B. 686472aa27 Better multiplayer 2020-10-21 15:51:46 +02:00
13 changed files with 149 additions and 70 deletions

View File

@ -8,7 +8,7 @@
## Hello other ## Hello other
- [ ] Multiplayer - [~] Multiplayer
- [ ] Chat - [ ] Chat
- [~] Authentication - [~] Authentication
- [x] Compression - [x] Compression
@ -19,10 +19,12 @@
## Hello world ## Hello world
- [~] Map stream - [~] Map stream
- Local prediction - [ ] Modifications compression
- [ ] Local prediction
- [ ] Contouring service - [ ] Contouring service
- [~] Edit - [~] Edit
- Local prediction - [ ] More types
- [ ] Local prediction
- [~] Occlusion Culling - [~] Occlusion Culling
- [ ] Iterator ray - [ ] Iterator ray
- [ ] Cast from chunk center - [ ] Cast from chunk center

View File

@ -180,7 +180,7 @@ void Client::run(server_handle* const localHandle) {
} }
window.waitTargetFPS(); window.waitTargetFPS();
} while (!(inputs.isDown(Input::Quit) || window.shouldClose())); } while (!(inputs.isDown(Input::Quit) || window.shouldClose() || world->isDisconnected()));
options.contouring = state.contouring->getOptions(); options.contouring = state.contouring->getOptions();
world.reset(); world.reset();

View File

@ -198,7 +198,13 @@ void DistantUniverse::pullNetwork(voxel_pos pos) {
break; break;
} }
}, },
[](disconnect_reason){ }); [](disconnect_reason reason){
if (reason == disconnect_reason::UNEXPECTED) {
LOG_E("Connection to server lost");
} else {
LOG_W("Disconnected from server with " << (int)reason);
}
});
} }
void DistantUniverse::emit(const action::packet &action) { void DistantUniverse::emit(const action::packet &action) {

View File

@ -18,6 +18,8 @@ namespace world::client {
ray_result raycast(const geometry::Ray &) const override; ray_result raycast(const geometry::Ray &) const override;
bool isDisconnected() const override { return peer.isDisconnected(); }
protected: protected:
void pullNetwork(voxel_pos); void pullNetwork(voxel_pos);

View File

@ -15,7 +15,7 @@ LocalUniverse::LocalUniverse(server_handle *const handle, const std::string& con
Window::wait(); Window::wait();
} }
handle->onUpdate = std::function([&](const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) { handle->onUpdate = std::function([&](const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) {
contouring->onUpdate(pos, offset, data, neighbors); contouring->onUpdate(pos, this->last_chunk - offset, data, neighbors);
}); });
} }
LocalUniverse::~LocalUniverse() { LocalUniverse::~LocalUniverse() {

View File

@ -15,6 +15,8 @@ namespace world::client {
ray_result raycast(const geometry::Ray &ray) const override; ray_result raycast(const geometry::Ray &ray) const override;
bool isDisconnected() const override { return !handle->running; }
protected: protected:
server_handle *const handle; server_handle *const handle;

View File

@ -32,6 +32,8 @@ namespace world::client {
return contouring.get(); return contouring.get();
} }
virtual bool isDisconnected() const = 0;
//TODO: move to ClientUniverse //TODO: move to ClientUniverse
//void getEntitiesModels(const std::function<void(const std::vector<glm::mat4> &, buffer::Abstract *const)> &draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density); //void getEntitiesModels(const std::function<void(const std::vector<glm::mat4> &, buffer::Abstract *const)> &draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density);

View File

@ -173,6 +173,17 @@ namespace data::generational {
} }
} }
template<typename predicate>
bool contains(predicate fn) const {
for (size_t i = 0; i < entries.size(); i++) {
const auto &entry = entries[i];
if(entry.value.has_value() && fn(id(i, entry.generation), entry.value.value())) {
return true;
}
}
return false;
}
template<typename extractor> template<typename extractor>
void extract(extractor fn) { void extract(extractor fn) {
for (size_t i = 0; i < entries.size(); i++) { for (size_t i = 0; i < entries.size(); i++) {

View File

@ -97,8 +97,6 @@ public:
break; break;
} }
} }
TracyPlot("CltNetUp", (int64_t)host->outgoingBandwidth);
TracyPlot("CltNetDown", (int64_t)host->incomingBandwidth);
TracyPlot("CltNetRTT", (int64_t)peer->roundTripTime); TracyPlot("CltNetRTT", (int64_t)peer->roundTripTime);
} }
@ -124,6 +122,7 @@ public:
} }
constexpr bool isReady() const { return ready; } constexpr bool isReady() const { return ready; }
constexpr bool isDisconnected() const { return peer->state == ENET_PEER_STATE_DISCONNECTED; }
constexpr salt_t getSalt() const { return salt; } constexpr salt_t getSalt() const { return salt; }
protected: protected:

View File

@ -7,20 +7,25 @@ namespace net {
class Server { class Server {
public: public:
Server(const connection& ct, size_t connections) { Server(const connection& ct, int connections) {
auto addr = [&] { if(connections > 0) {
if (auto addr = ct.toAddress()) auto addr = [&] {
return addr.value(); if (auto addr = ct.toAddress())
return addr.value();
FATAL("Invalid ip address format"); FATAL("Invalid ip address format");
}(); }();
host = enet_host_create(&addr, connections, CHANNEL_COUNT, 0, 0); host = enet_host_create(&addr, connections, CHANNEL_COUNT, 0, 0);
if(host == nullptr) { if(host == nullptr) {
FATAL("Network server creation failed"); FATAL("Network server creation failed");
}
LOG_I("Listening on " << ct);
} else {
host = nullptr;
LOG_D("Local only server");
} }
LOG_I("Listening on " << ct);
} }
~Server() { ~Server() {
enet_host_destroy(host); enet_host_destroy(host);
@ -28,6 +33,9 @@ public:
template<typename C, typename D, typename R> template<typename C, typename D, typename R>
void pull(C onConnect, D onDisconnect, R onData, int delay = 10, int count = 10) { void pull(C onConnect, D onDisconnect, R onData, int delay = 10, int count = 10) {
if (host == nullptr)
return;
ENetEvent event; ENetEvent event;
for(int i = 0; i < count && enet_host_service(host, &event, delay) > 0; i++) { for(int i = 0; i < count && enet_host_service(host, &event, delay) > 0; i++) {
switch(event.type) { switch(event.type) {
@ -49,8 +57,14 @@ public:
break; break;
} }
} }
TracyPlot("SrvNetUp", (int64_t)host->outgoingBandwidth); TracyPlot("SrvNetUpData", (int64_t)host->totalSentData);
TracyPlot("SrvNetDown", (int64_t)host->incomingBandwidth); host->totalSentData = 0;
TracyPlot("SrvNetUpPackets", (int64_t)host->totalSentPackets);
host->totalSentPackets = 0;
TracyPlot("SrvNetDownData", (int64_t)host->totalReceivedData);
host->totalReceivedData = 0;
TracyPlot("SrvNetDownPackets", (int64_t)host->totalReceivedPackets);
host->totalReceivedPackets = 0;
} }
void disconnect(peer_t *peer, disconnect_reason reason) const { void disconnect(peer_t *peer, disconnect_reason reason) const {
@ -91,7 +105,8 @@ public:
/// Send to all connected peers /// Send to all connected peers
void broadcast(packet_t* packet, channel_type channel) { void broadcast(packet_t* packet, channel_type channel) {
enet_host_broadcast(host, (enet_uint8)channel, packet); if (host != nullptr)
enet_host_broadcast(host, (enet_uint8)channel, packet);
} }
void broadcast(server_packet_type type, const void *data, size_t size, channel_type channel, std::optional<enet_uint32> flags = {}) { void broadcast(server_packet_type type, const void *data, size_t size, channel_type channel, std::optional<enet_uint32> flags = {}) {
broadcast(makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0)).get(), channel); broadcast(makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0)).get(), channel);

View File

@ -9,7 +9,7 @@ SharedUniverse::SharedUniverse(const options &o, server_handle *const localHandl
const auto id = entities.at(PLAYER_ENTITY_ID).instances.emplace(Entity::Instance{spawnPoint}); const auto id = entities.at(PLAYER_ENTITY_ID).instances.emplace(Entity::Instance{spawnPoint});
assert(id == PLAYER_ENTITY_ID); assert(id == PLAYER_ENTITY_ID);
localHandle->teleport = spawnPoint; localHandle->teleport = spawnPoint;
movedPlayers.push_back(id); movedPlayers.insert(id);
localHandle->areas = (world::client::area_map*)(&areas); //WONT FIX: templated area localHandle->areas = (world::client::area_map*)(&areas); //WONT FIX: templated area
localHandle->emit = std::function([&](const world::action::packet &packet) { localHandle->emit = std::function([&](const world::action::packet &packet) {

View File

@ -175,29 +175,36 @@ void Universe::update(float deltaTime) {
pullNetwork(); pullNetwork();
if(entities.at(PLAYER_ENTITY_ID).instances.empty()) std::vector<voxel_pos> moves;
return; {
moves.reserve(movedPlayers.size());
for (const auto& id: movedPlayers) {
if (auto player = findEntity(PLAYER_ENTITY_ID, id))
moves.push_back(player->pos.as_voxel());
}
movedPlayers.clear();
}
const auto pos = entities.at(PLAYER_ENTITY_ID).instances.at(PLAYER_ENTITY_ID).pos.as_voxel(); if (!moves.empty()) {
const auto chunkChange = !movedPlayers.empty();
movedPlayers.clear();
if(chunkChange) {
ZoneScopedN("Far"); ZoneScopedN("Far");
bool extracted = false; bool extracted = false;
far_areas.extract([&](area_id id, Area::params params) { far_areas.extract([&](area_id id, Area::params params) {
if (const chunk_pos diff = glm::divide(pos - params.center); for(const auto& move: moves) {
glm::length2(diff) > glm::pow2(loadDistance + params.radius)) if(const chunk_pos diff = glm::divide(move - params.center);
return false; glm::length2(diff) <= glm::pow2(loadDistance + params.radius)) {
LOG_I("Load area " << id.index); LOG_I("Load area " << id.index);
areas.emplace(id, std::make_shared<Area>(params)); areas.emplace(id, std::make_shared<Area>(params));
extracted = true; extracted = true;
return true; return true;
}
}
return false;
}); });
if(extracted) if(extracted)
broadcastAreas(); broadcastAreas();
} }
const auto &players = entities.at(PLAYER_ENTITY_ID).instances;
{ // Update alive areas { // Update alive areas
ZoneScopedN("World"); ZoneScopedN("World");
#if TRACY_ENABLE #if TRACY_ENABLE
@ -209,10 +216,19 @@ void Universe::update(float deltaTime) {
auto it = areas.begin(); auto it = areas.begin();
while (it != areas.end()) { while (it != areas.end()) {
ZoneScopedN("Area"); ZoneScopedN("Area");
const bool chunkChangeArea = (false && it->first == 1 && it->second->move(glm::vec3(deltaTime))) || chunkChange; // TODO: area.velocity //FIXME: const auto areaChunkChange = it->second->move(glm::vec3(deltaTime));
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel()); const auto areaRange = glm::pow2(keepDistance + it->second->getChunks().getRadius());
const auto areaDiff = glm::divide(it->second->getOffset().as_voxel());
std::vector<chunk_pos> inAreaPlayers;
players.iter([&](entity_id, Entity::Instance player) {
const chunk_pos diff = glm::divide(player.pos.as_voxel() - it->second->getOffset().as_voxel());
if (glm::length2(diff) <= areaRange)
inAreaPlayers.push_back(diff);
});
auto &chunks = it->second->setChunks(); auto &chunks = it->second->setChunks();
if (glm::length2(diff) > glm::pow2(keepDistance + it->second->getChunks().getRadius())) { if (inAreaPlayers.empty()) {
auto it_c = chunks.begin(); auto it_c = chunks.begin();
while(it_c != chunks.end()) { while(it_c != chunks.end()) {
saveQueue.emplace(*it, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second))); saveQueue.emplace(*it, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second)));
@ -230,35 +246,48 @@ void Universe::update(float deltaTime) {
ZoneScopedN("Alive"); ZoneScopedN("Alive");
auto it_c = chunks.begin(); auto it_c = chunks.begin();
while(it_c != chunks.end()) { while(it_c != chunks.end()) {
if (glm::length2(diff - it_c->first) > glm::pow2(keepDistance)) { if ([&] {
saveQueue.emplace(*it, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second))); //MAYBE: take look const auto keepDist = glm::pow2(keepDistance);
lazyArea = false; for(const auto& diff: inAreaPlayers) {
it_c = chunks.erase(it_c); if (glm::length2(diff - it_c->first) < keepDist)
}else { return true;
updateChunk(it, it_c, diff, deltaTime); }
return false;
}()) {
updateChunk(it, it_c, areaDiff, deltaTime);
++it_c; ++it_c;
#if TRACY_ENABLE #if TRACY_ENABLE
chunk_count++; chunk_count++;
#endif #endif
} else {
saveQueue.emplace(*it, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second))); //MAYBE: take look
lazyArea = false;
it_c = chunks.erase(it_c);
} }
} }
} }
if (chunkChangeArea) { // Enqueue missing chunks { // Enqueue missing chunks
ZoneScopedN("Missing"); ZoneScopedN("Missing");
auto handle = loadQueue.inserter(); auto handle = loadQueue.inserter();
//TODO: need dist so no easy sphere fill for (const auto& to: moves) {
for (int x = -loadDistance; x <= loadDistance; x++) { const chunk_pos diff = glm::divide(to - it->second->getOffset().as_voxel());
for (int y = -loadDistance; y <= loadDistance; y++) { if (glm::length2(diff) > areaRange)
for (int z = -loadDistance; z <= loadDistance; z++) { continue;
const auto dist2 = x * x + y * y + z * z;
if (dist2 <= loadDistance * loadDistance) { //TODO: need dist so no easy sphere fill
const auto p = diff + chunk_pos(x, y, z); for (int x = -loadDistance; x <= loadDistance; x++) {
if (chunks.inRange(p) && chunks.find(p) == chunks.end()) { for (int y = -loadDistance; y <= loadDistance; y++) {
handle.first(std::make_pair(it->first, p), it->second, -dist2); for (int z = -loadDistance; z <= loadDistance; z++) {
lazyArea = false; const auto dist2 = x * x + y * y + z * z;
if (dist2 <= loadDistance * loadDistance) {
const auto p = diff + chunk_pos(x, y, z);
if (chunks.inRange(p) && chunks.find(p) == chunks.end()) {
handle.first(std::make_pair(it->first, p), it->second, -dist2);
lazyArea = false;
}
} }
} }}}
}}} }
if(!lazyArea) if(!lazyArea)
loadQueue.notify_all(); loadQueue.notify_all();
} }
@ -270,7 +299,14 @@ void Universe::update(float deltaTime) {
region_count += unique->size(); region_count += unique->size();
#endif #endif
for (auto it_r = unique->begin(); it_r != unique->end(); ++it_r) { for (auto it_r = unique->begin(); it_r != unique->end(); ++it_r) {
if (glm::length2(diff - glm::lvec3(it_r->first) * glm::lvec3(REGION_LENGTH)) > glm::pow2(keepDistance + REGION_LENGTH * 2)) { if([&] {
const auto keepDist = glm::pow2(keepDistance + REGION_LENGTH * 2);
for(const auto& diff: inAreaPlayers) {
if (glm::length2(diff - glm::lvec3(it_r->first) * glm::lvec3(REGION_LENGTH)) <= keepDist)
return false;
}
return true;
}()) {
unique->erase(it_r); //FIXME: may wait for os file access (long) unique->erase(it_r); //FIXME: may wait for os file access (long)
break; //NOTE: save one only max per frame break; //NOTE: save one only max per frame
} }
@ -288,7 +324,7 @@ void Universe::update(float deltaTime) {
TracyPlot("ChunkUnload", static_cast<int64_t>(saveQueue.size())); TracyPlot("ChunkUnload", static_cast<int64_t>(saveQueue.size()));
#endif #endif
} }
{ // Update entities /* FIXME: { // Update entities
ZoneScopedN("Entities"); ZoneScopedN("Entities");
#if TRACY_ENABLE #if TRACY_ENABLE
size_t entity_count = 0; size_t entity_count = 0;
@ -303,12 +339,14 @@ void Universe::update(float deltaTime) {
#endif #endif
inst.pos += inst.velocity * deltaTime; inst.pos += inst.velocity * deltaTime;
return glm::length2(glm::divide(pos - inst.pos.as_voxel())) > glm::pow2(keepDistance); return glm::length2(glm::divide(pos - inst.pos.as_voxel())) > glm::pow2(keepDistance);
//MAYBE: Store in region ?
//MAYBE: Save to files
}); });
}); });
#if TRACY_ENABLE #if TRACY_ENABLE
TracyPlot("EntityCount", static_cast<int64_t>(entity_count)); TracyPlot("EntityCount", static_cast<int64_t>(entity_count));
#endif #endif
} }*/
{ // Store loaded chunks { // Store loaded chunks
ZoneScopedN("Load"); ZoneScopedN("Load");
@ -316,8 +354,7 @@ void Universe::update(float deltaTime) {
for (auto handle = loadedQueue.extractor(); handle.first(loaded);) { for (auto handle = loadedQueue.extractor(); handle.first(loaded);) {
if (const auto it = areas.find(loaded.first.first); it != areas.end()) { if (const auto it = areas.find(loaded.first.first); it != areas.end()) {
it->second->setChunks().emplace(loaded.first.second, loaded.second); it->second->setChunks().emplace(loaded.first.second, loaded.second);
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel()); loadChunk(loaded.first, glm::divide(it->second->getOffset().as_voxel()), it->second->getChunks());
loadChunk(loaded.first, diff, it->second->getChunks());
// MAYBE: limit chunks per update // MAYBE: limit chunks per update
host.broadcast(serializeChunk(loaded), net::channel_type::RELIABLE); host.broadcast(serializeChunk(loaded), net::channel_type::RELIABLE);
} }
@ -350,15 +387,17 @@ void Universe::pullNetwork() {
{ {
auto player = findEntity(PLAYER_ENTITY_ID, client->instanceId); auto player = findEntity(PLAYER_ENTITY_ID, client->instanceId);
host.sendTo(peer, server_packet_type::TELEPORT, player->pos.as_voxel(), channel_type::RELIABLE); host.sendTo(peer, server_packet_type::TELEPORT, player->pos.as_voxel(), channel_type::RELIABLE);
movedPlayers.push_back(client->instanceId); movedPlayers.insert(client->instanceId);
} }
broadcastAreas(); broadcastAreas();
}, },
[](peer_t *peer, disconnect_reason reason) { [&](peer_t *peer, disconnect_reason reason) {
ZoneScopedN("Disconnect"); ZoneScopedN("Disconnect");
LOG_I("Client disconnect from " << peer->address << " with " << (enet_uint32)reason); LOG_I("Client disconnect from " << peer->address << " with " << (enet_uint32)reason);
if (const auto data = Server::GetPeerData<net_client>(peer); data != nullptr) if (const auto data = Server::GetPeerData<net_client>(peer)) {
entities.at(PLAYER_ENTITY_ID).instances.free(data->instanceId);
delete data; delete data;
}
}, },
[&](peer_t *peer, packet_t* packet, channel_type) { [&](peer_t *peer, packet_t* packet, channel_type) {
ZoneScopedN("Data"); ZoneScopedN("Data");
@ -554,11 +593,12 @@ Universe::Entity::Instance* Universe::findEntity(entity_id type, entity_id id) {
bool Universe::movePlayer(data::generational::id id, glm::ifvec3 pos) { bool Universe::movePlayer(data::generational::id id, glm::ifvec3 pos) {
if (auto player = findEntity(PLAYER_ENTITY_ID, id)) { if (auto player = findEntity(PLAYER_ENTITY_ID, id)) {
if(player->pos.as_voxel() == pos.as_voxel()) const auto initialPos = player->pos.as_voxel();
if (initialPos == pos.as_voxel())
return true; return true;
//TODO: check dist + collision from a to b //TODO: check dist + collision from a to b
movedPlayers.push_back(id); movedPlayers.insert(id);
player->pos = pos; player->pos = pos;
return true; return true;
} else } else

View File

@ -79,7 +79,7 @@ namespace world::server {
virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime); virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime);
virtual void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &); virtual void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &);
std::vector<data::generational::id> movedPlayers; robin_hood::unordered_set<data::generational::id> movedPlayers;
voxel_pos spawnPoint; voxel_pos spawnPoint;
/// Alive areas containing chunks /// Alive areas containing chunks