2020-09-24 19:55:44 +00:00
|
|
|
#include "DistantUniverse.hpp"
|
|
|
|
|
|
|
|
#include <Tracy.hpp>
|
|
|
|
|
|
|
|
#include "Area.hpp"
|
|
|
|
#include "../contouring/Abstract.hpp"
|
|
|
|
#include "../../core/world/raycast.hpp"
|
2020-11-03 22:04:43 +00:00
|
|
|
#include "../../core/net/io.hpp"
|
2020-09-24 19:55:44 +00:00
|
|
|
#include "../../core/utils/logger.hpp"
|
|
|
|
#include "Chunk.hpp"
|
|
|
|
|
|
|
|
using namespace world::client;
|
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
DistantUniverse::DistantUniverse(const connection& ct, const options& opt, const std::string& contouring): Universe(contouring),
|
|
|
|
loadDistance(opt.loadDistance), keepDistance(opt.keepDistance), serverDistance(0),
|
|
|
|
peer(ct, [&](const data::out_view& buf, net::PacketFlags flags){ return onPacket(buf, flags); }) { }
|
2020-09-24 19:55:44 +00:00
|
|
|
DistantUniverse::~DistantUniverse() { }
|
|
|
|
|
|
|
|
void DistantUniverse::update(voxel_pos pos, float deltaTime) {
|
|
|
|
const auto cur_chunk = glm::divide(pos);
|
|
|
|
const auto chunkChange = cur_chunk != last_chunk;
|
|
|
|
last_chunk = cur_chunk;
|
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
{ //NOTE: triggers onPacket
|
|
|
|
ZoneScopedN("Pull");
|
|
|
|
peer.pull();
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
|
|
|
|
{ // Update alive areas
|
|
|
|
ZoneScopedN("World");
|
|
|
|
for (auto& area: areas) {
|
|
|
|
ZoneScopedN("Area");
|
|
|
|
const bool chunkChangeArea = (false && area.second->move(glm::vec3(deltaTime))) || chunkChange; // TODO: area.velocity
|
|
|
|
const chunk_pos diff = glm::divide(pos - area.second->getOffset().as_voxel());
|
|
|
|
auto &chunks = area.second->setChunks();
|
|
|
|
if (glm::length2(diff) <= glm::pow2(keepDistance + area.second->getChunks().getRadius())) {
|
|
|
|
ZoneScopedN("Alive");
|
|
|
|
auto it_c = chunks.begin();
|
|
|
|
while (it_c != chunks.end()) {
|
|
|
|
if (glm::length2(diff - it_c->first) > glm::pow2(keepDistance)) {
|
|
|
|
it_c = chunks.erase(it_c);
|
|
|
|
} else {
|
2020-11-04 21:04:37 +00:00
|
|
|
if(const auto neighbors = std::dynamic_pointer_cast<world::client::EdittableChunk>(it_c->second)->update(deltaTime, true /*MAYBE: random update*/)) {
|
2020-09-24 19:55:44 +00:00
|
|
|
contouring->onUpdate(std::make_pair(area.first, it_c->first), diff, chunks, neighbors.value());
|
|
|
|
}
|
|
|
|
++it_c;
|
|
|
|
}
|
|
|
|
}
|
2020-11-03 22:04:43 +00:00
|
|
|
if (chunkChangeArea || mayQueryChunks) { // Request missing chunks
|
2020-09-25 18:50:46 +00:00
|
|
|
ZoneScopedN("Missing");
|
|
|
|
std::vector<chunk_pos> missing;
|
2020-10-31 17:53:12 +00:00
|
|
|
std::vector<long> missingDist;
|
2020-09-25 18:50:46 +00:00
|
|
|
//TODO: use easy sphere fill
|
2020-09-27 20:25:35 +00:00
|
|
|
const int queryDistance = std::min(loadDistance, serverDistance);
|
|
|
|
for (int x = -queryDistance; x <= queryDistance; x++) {
|
|
|
|
for (int y = -queryDistance; y <= queryDistance; y++) {
|
|
|
|
for (int z = -queryDistance; z <= queryDistance; z++) {
|
2020-09-25 18:50:46 +00:00
|
|
|
const auto dist2 = x * x + y * y + z * z;
|
2020-10-20 19:08:40 +00:00
|
|
|
const auto p = diff + chunk_pos(x, y, z);
|
|
|
|
if (dist2 <= queryDistance * queryDistance && chunks.inRange(p)) {
|
|
|
|
if (chunks.find(p) != chunks.end()) {
|
|
|
|
contouring->onNotify(std::make_pair(area.first, p), diff, chunks);
|
|
|
|
} else {
|
2020-10-31 17:53:12 +00:00
|
|
|
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);
|
2020-09-25 18:50:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}}}
|
|
|
|
if(!missing.empty()) {
|
2020-11-03 22:04:43 +00:00
|
|
|
auto packet = net::PacketWriter(net::client_packet_type::MISSING_CHUNKS, sizeof(area_id) + missing.size() * sizeof(chunk_pos));
|
2020-09-25 18:50:46 +00:00
|
|
|
packet.write(area.first);
|
|
|
|
packet.write(missing.data(), missing.size() * sizeof(chunk_pos));
|
2020-11-03 22:04:43 +00:00
|
|
|
peer.send(packet.finish());
|
2020-09-25 18:50:46 +00:00
|
|
|
}
|
2020-10-31 17:53:12 +00:00
|
|
|
mayQueryChunks = false;
|
2020-09-25 18:50:46 +00:00
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
contouring->update(pos, areas);
|
|
|
|
}
|
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
|
2020-09-24 19:55:44 +00:00
|
|
|
using namespace net;
|
2020-09-27 20:25:35 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
auto packet = PacketReader(buf);
|
|
|
|
server_packet_type type;
|
|
|
|
if(!packet.read(type)) {
|
|
|
|
LOG_D("Empty packet");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
switch (type) {
|
|
|
|
case server_packet_type::QUIT: {
|
|
|
|
bool is_app = false;
|
|
|
|
is_app &= packet.read(is_app);
|
|
|
|
uint16_t reason = 0;
|
|
|
|
packet.read(reason);
|
|
|
|
if (is_app) {
|
|
|
|
LOG_E("Disconnected from server (" << reason << ')');
|
|
|
|
} else {
|
|
|
|
LOG_D("Connection lost (" << reason << ')');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::CAPABILITIES:
|
|
|
|
return packet.read(serverDistance);
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::COMPRESSION: {
|
|
|
|
const auto remain = packet.readAll();
|
|
|
|
dict.emplace(remain.data(), remain.size());
|
|
|
|
LOG_T("Compression dictionnary loaded");
|
|
|
|
mayQueryChunks = true;
|
|
|
|
break;
|
|
|
|
}
|
2020-10-20 19:08:40 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::TELEPORT: {
|
|
|
|
voxel_pos pos;
|
|
|
|
if (packet.read(playerId) && packet.read(pos))
|
|
|
|
onTeleport(pos);
|
|
|
|
break;
|
|
|
|
}
|
2020-10-21 17:55:32 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::MESSAGE: {
|
|
|
|
const auto remain = packet.readAll();
|
|
|
|
onMessage(std::string((const char*)remain.data(), remain.size()));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case server_packet_type::AREAS: {
|
|
|
|
while(!packet.isFull()) {
|
|
|
|
area_id id;
|
|
|
|
world::Area::params p;
|
|
|
|
if(!packet.read(id) || !packet.read(p))
|
2020-09-24 19:55:44 +00:00
|
|
|
break;
|
2020-11-03 22:04:43 +00:00
|
|
|
|
|
|
|
if (auto it = areas.find(id); it != areas.end()) {
|
|
|
|
std::dynamic_pointer_cast<Area>(it->second)->update(p);
|
|
|
|
} else {
|
|
|
|
areas.emplace(id, std::make_shared<Area>(p));
|
2020-09-24 19:55:44 +00:00
|
|
|
}
|
2020-11-03 22:04:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::CHUNK: {
|
|
|
|
ZoneScopedN("Chunk");
|
|
|
|
if (!dict.has_value())
|
|
|
|
break;
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
if (packet.isFull()) {
|
|
|
|
mayQueryChunks = true;
|
|
|
|
break;
|
|
|
|
}
|
2020-10-31 17:53:12 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
area_<chunk_pos> pos;
|
|
|
|
if(!packet.read(pos))
|
|
|
|
break;
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
auto it = areas.find(pos.first);
|
|
|
|
if(it == areas.end()) {
|
|
|
|
LOG_W("Chunk area not found " << pos.first.index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(!it->second->getChunks().inRange(pos.second)) {
|
|
|
|
LOG_W("Chunk out of area " << pos.first.index);
|
|
|
|
break;
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
std::vector<char> buffer;
|
|
|
|
if(auto err = dict.value().decompress(packet.readAll(), buffer)) {
|
|
|
|
LOG_E("Corrupted chunk packet " << err.value());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
data::vec_istream idata(buffer);
|
|
|
|
std::istream iss(&idata);
|
|
|
|
auto ck = std::make_shared<world::client::EdittableChunk>(iss);
|
|
|
|
ck->invalidate(geometry::Faces::All);
|
|
|
|
auto ptr = std::dynamic_pointer_cast<world::Chunk>(ck);
|
|
|
|
auto &chunks = it->second->setChunks();
|
|
|
|
if (auto it_c = chunks.find(pos.second); it_c != chunks.end()) {
|
|
|
|
it_c->second = ptr;
|
|
|
|
} else {
|
|
|
|
chunks.emplace(pos.second, ptr);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::EDITS: {
|
|
|
|
ZoneScopedN("Edits");
|
|
|
|
area_id id;
|
|
|
|
if(!packet.read(id))
|
|
|
|
break;
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
auto it = areas.find(id);
|
|
|
|
if(it == areas.end()) {
|
|
|
|
LOG_W("Edit area not found " << id.index);
|
|
|
|
break;
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
while(!packet.isFull()) {
|
|
|
|
chunk_pos pos = chunk_pos(INT_MAX);
|
|
|
|
packet.read(pos);
|
|
|
|
chunk_voxel_idx count = 0;
|
|
|
|
packet.read(count);
|
|
|
|
if (auto ptr = it->second->setChunks().findInRange(pos)) {
|
|
|
|
auto chunk = std::dynamic_pointer_cast<world::client::EdittableChunk>(ptr.value());
|
|
|
|
for (auto i = 0; i < count && !packet.isFull(); i++) {
|
|
|
|
Chunk::Edit edit;
|
|
|
|
packet.read(edit);
|
|
|
|
chunk->apply(edit);
|
2020-10-23 13:19:44 +00:00
|
|
|
}
|
2020-11-03 22:04:43 +00:00
|
|
|
} else {
|
|
|
|
packet.skip(count * sizeof(Chunk::Edit));
|
2020-10-23 13:19:44 +00:00
|
|
|
}
|
2020-11-03 22:04:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-10-23 13:19:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::ENTITY_TYPES: {
|
|
|
|
entities.clear();
|
|
|
|
while(!packet.isFull()) {
|
|
|
|
size_t index;
|
|
|
|
glm::vec3 size;
|
|
|
|
glm::vec3 scale;
|
|
|
|
if(packet.read(index) && packet.read(size) && packet.read(scale)) {
|
|
|
|
entities.set_emplace(index, size, scale);
|
2020-10-23 13:19:44 +00:00
|
|
|
}
|
2020-11-03 22:04:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-10-23 13:19:44 +00:00
|
|
|
|
2020-11-03 22:04:43 +00:00
|
|
|
case server_packet_type::ENTITIES: {
|
|
|
|
while(!packet.isFull()) {
|
|
|
|
size_t entity_idx;
|
|
|
|
size_t count;
|
|
|
|
if (!(packet.read(entity_idx) && packet.read(count)))
|
2020-09-24 19:55:44 +00:00
|
|
|
break;
|
2020-11-03 22:04:43 +00:00
|
|
|
|
|
|
|
if (auto entity = entities.get(entity_idx)) {
|
|
|
|
entity->instances.clear();
|
|
|
|
for (size_t i = 0; i < count && !packet.isFull(); i++) {
|
|
|
|
size_t idx;
|
|
|
|
glm::ifvec3 pos;
|
|
|
|
glm::vec3 vel;
|
|
|
|
if (packet.read(idx) && packet.read(pos) && packet.read(vel))
|
|
|
|
entity->instances.set_emplace(idx, Universe::Entity::Instance{pos, vel});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOG_W("Unknown entity type " << entity_idx);
|
|
|
|
packet.skip(count * (sizeof(size_t) + sizeof(glm::ifvec3) + sizeof(glm::vec3)));
|
|
|
|
}
|
2020-09-24 19:55:44 +00:00
|
|
|
}
|
2020-11-03 22:04:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
LOG_W("Bad packet from server");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
2020-09-24 19:55:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DistantUniverse::emit(const action::packet &action) {
|
|
|
|
if(const auto move = std::get_if<action::Move>(&action)) {
|
2020-11-03 22:04:43 +00:00
|
|
|
peer.send(net::PacketWriter::Of(net::client_packet_type::MOVE, move->pos), net::client::queue::MOVE, 1);
|
2020-10-21 17:55:32 +00:00
|
|
|
} else if(const auto message = std::get_if<action::Message>(&action)) {
|
2020-11-03 22:04:43 +00:00
|
|
|
peer.send(net::PacketWriter::Of(net::client_packet_type::MESSAGE, message->text.data(), message->text.size()));
|
2020-10-23 20:50:00 +00:00
|
|
|
} else if(const auto fill = std::get_if<action::FillShape>(&action)) {
|
2020-11-03 22:04:43 +00:00
|
|
|
peer.send(net::PacketWriter::Of(net::client_packet_type::FILL_SHAPE, *fill));
|
2020-09-24 19:55:44 +00:00
|
|
|
} else {
|
|
|
|
LOG_W("Bad action " << action.index());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Universe::ray_result DistantUniverse::raycast(const geometry::Ray &ray) const {
|
|
|
|
return Raycast(ray, areas);
|
2020-10-23 13:19:44 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:22:32 +00:00
|
|
|
bool DistantUniverse::isAreaFree(const area_<voxel_pos> &pos, const geometry::Shape shape, const uint16_t radius) const {
|
|
|
|
if (const auto it = areas.find(pos.first); it != areas.end()) {
|
|
|
|
const auto center = pos.second + it->second->getOffset().as_voxel();
|
|
|
|
return !entities.contains([&](size_t, const Entity &entity) {
|
|
|
|
return entity.instances.contains([&](size_t, const Universe::Entity::Instance &inst) {
|
|
|
|
return geometry::InShape(shape, center, radius, inst.pos.as_voxel(), entity.size);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-23 13:19:44 +00:00
|
|
|
void DistantUniverse::getEntitiesModels(const std::function<void(size_t, const std::vector<glm::mat4>&)>& call,
|
|
|
|
const std::optional<geometry::Frustum>& frustum, const glm::llvec3& offset, int density)
|
|
|
|
{
|
|
|
|
std::vector<glm::mat4> mats;
|
|
|
|
entities.iter([&](size_t eId, const Entity &entity) {
|
|
|
|
entity.instances.iter([&](size_t iId, const Universe::Entity::Instance &inst) {
|
|
|
|
if (eId == PLAYER_ENTITY_ID.index && iId == playerId)
|
|
|
|
return;
|
|
|
|
|
|
|
|
glm::mat4 tmp;
|
|
|
|
if (contouring::Abstract::CullEntity(tmp, entity.size, entity.scale, inst.pos, frustum, offset, density))
|
|
|
|
mats.push_back(tmp);
|
|
|
|
});
|
|
|
|
if(!mats.empty()) {
|
|
|
|
call(eId, mats);
|
|
|
|
mats.resize(0);
|
|
|
|
}
|
|
|
|
});
|
2020-09-24 19:55:44 +00:00
|
|
|
}
|