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

Single player server

parents 6dfea31e fd49ef3f
......@@ -11,16 +11,17 @@
- [ ] Chat
- [~] Authentication
- [ ] Compression
- [x] Compression
- [ ] Encryption
- [x] Embedded
- [ ] Standalone
## Hello world
- [ ] Map stream
- [~] Map stream
- [ ] Contouring service
- [ ] Edit
- [~] Edit
- Local prediction
- [~] Occlusion Culling
- [ ] Iterator ray
- [ ] Cast from chunk center
......
......@@ -4,18 +4,18 @@
namespace world::client {
/// Area (aka big group of client::Chunk)
struct Area: public world::Area {
struct Area final: public world::Area {
public:
struct params {
voxel_pos center;
int radius;
std::optional<double> curvature;
};
Area(const params& p): world::Area(p.center, p.radius), curvature(p.curvature) { }
std::optional<double> getCurvature() const override { return curvature; }
void update(const params& p) {
assert(getChunks().getRadius() == p.radius);
center = p.center;
curvature = p.curvature;
}
private:
std::optional<double> curvature;
};
......
......@@ -4,7 +4,7 @@
namespace world::client {
class Chunk: public EdittableChunk {
class Chunk final: public EdittableChunk {
public:
Chunk(): world::Chunk(), EdittableChunk() { }
};
......
#include "DistantUniverse.hpp"
#include "Area.hpp"
#include "../contouring/Abstract.hpp"
#include "../../core/utils/logger.hpp"
using namespace world::client;
DistantUniverse::DistantUniverse(const connection& ct, const options& opt): Universe(), net::Client(ct) { }
DistantUniverse::~DistantUniverse() { }
void DistantUniverse::update(voxel_pos pos, float) {
const auto cur_chunk = glm::divide(pos);
const auto chunkChange = cur_chunk != last_chunk;
last_chunk = cur_chunk;
net::Client::pull();
if(chunkChange) {
//NOTE: unprecise
for(const auto& area: areas) {
const chunk_pos diff = glm::divide(pos - area.second->getOffset().as_voxel());
for(const auto& chunk: area.second->getChunks()) {
contouring->onNotify(std::make_pair(area.first, chunk.first), diff, area.second->getChunks());
}
}
}
contouring->update(pos, areas);
}
void DistantUniverse::emit(const action::packet &action) {
if(const auto move = std::get_if<action::Move>(&action)) {
send(net::client_packet_type::MOVE, move->pos, net::channel_type::NOTIFY);
} else {
LOG_W("Bad action " << action.index());
}
}
Universe::ray_result DistantUniverse::raycast(const geometry::Ray &) const { return std::monostate{}; }
\ No newline at end of file
#include "DistantUniverse.hpp"
#include <Tracy.hpp>
#include "Area.hpp"
#include "../contouring/Abstract.hpp"
#include "../../core/world/raycast.hpp"
#include "../../core/utils/logger.hpp"
#include "Chunk.hpp"
using namespace world::client;
DistantUniverse::DistantUniverse(const connection& ct, const options& opt): Universe(), peer(ct),
loadDistance(opt.loadDistance), keepDistance(opt.keepDistance) { }
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;
pullNetwork();
{ // 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 {
if(const auto neighbors = std::dynamic_pointer_cast<world::client::EdittableChunk>(it_c->second)->update(deltaTime, true /*FIXME: rnd from contouring*/)) {
contouring->onUpdate(std::make_pair(area.first, it_c->first), diff, chunks, neighbors.value());
} else if(chunkChangeArea) {
contouring->onNotify(std::make_pair(area.first, it_c->first), diff, chunks);
}
++it_c;
}
}
}
}
}
contouring->update(pos, areas);
}
void DistantUniverse::pullNetwork() {
ZoneScopedN("Pull");
using namespace net;
peer.pull(
[&](packet_t* packet, channel_type){
const server_packet_type type = static_cast<server_packet_type>(*packet->data);
switch (type) {
case server_packet_type::COMPRESSION: {
if(dict.has_value())
break;
dict.emplace(packet->data + sizeof(server_packet_type), packet->dataLength - sizeof(server_packet_type));
LOG_D("Compression dictionnary loaded");
break;
}
case server_packet_type::AREAS: {
auto reader = PacketReader(packet, true);
while(!reader.isFull()) {
area_id id;
if(!reader.read(id))
break;
world::Area::params p;
if(!reader.read(p))
break;
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));
}
LOG_D("Area " << id.index);
}
break;
}
case server_packet_type::CHUNK: {
ZoneScopedN("Chunk");
if (!dict.has_value())
break;
auto reader = PacketReader(packet, true);
area_<chunk_pos> pos;
if(!reader.read(pos))
break;
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;
}
auto data = reader.remaning();
std::vector<char> buffer;
if(auto err = dict.value().decompress(data, buffer)) {
LOG_E("Corrupted chunk packet " << err.value());
break;
}
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;
}
case server_packet_type::EDITS: {
ZoneScopedN("Edits");
auto reader = PacketReader(packet, true);
area_id id;
if(!reader.read(id))
break;
auto it = areas.find(id);
if(it == areas.end()) {
LOG_W("Edit area not found " << id.index);
break;
}
while(!reader.isFull()) {
chunk_pos pos = chunk_pos(INT_MAX);
reader.read(pos);
chunk_voxel_idx count = 0;
reader.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 && !reader.isFull(); i++) {
Chunk::Edit edit;
reader.read(edit);
chunk->apply(edit);
}
} else {
reader.skip(count * sizeof(Chunk::Edit));
}
}
break;
}
default:
LOG_W("Bad packet from server");
break;
}
},
[](disconnect_reason){ });
}
void DistantUniverse::emit(const action::packet &action) {
if(const auto move = std::get_if<action::Move>(&action)) {
peer.send(net::client_packet_type::MOVE, move->pos, net::channel_type::NOTIFY);
} else if(const auto fillCube = std::get_if<action::FillCube>(&action)) {
peer.send(net::client_packet_type::FILL_CUBE, *fillCube, net::channel_type::RELIABLE);
} else {
LOG_W("Bad action " << action.index());
}
}
Universe::ray_result DistantUniverse::raycast(const geometry::Ray &ray) const {
return Raycast(ray, areas);
}
\ No newline at end of file
......@@ -2,12 +2,13 @@
#include "Universe.hpp"
#include "../../core/net/Client.hpp"
#include "../../core/utils/zctx.hpp"
namespace world::client {
class Area;
/// Whole universe container in network client
class DistantUniverse final: public Universe, net::Client {
class DistantUniverse final: public Universe {
public:
DistantUniverse(const connection&, const options &);
~DistantUniverse();
......@@ -18,9 +19,17 @@ namespace world::client {
ray_result raycast(const geometry::Ray &) const override;
protected:
void pullNetwork();
/// Alive areas containing chunks
area_map areas;
net::Client peer;
std::optional<zstd::read_dict_ctx> dict;
chunk_pos last_chunk = chunk_pos(INT_MAX);
int loadDistance;
int keepDistance;
};
}
\ No newline at end of file
#pragma once
#include <fstream>
#include <vector>
#include "../utils/logger.hpp"
namespace data {
class file_content: public std::vector<char> {
public:
// Read first
file_content(const std::vector<std::string>& paths): std::vector<char>() {
std::ifstream is = [&]() {
for(auto& path: paths) {
std::ifstream is(path, std::ios::in | std::ios::binary | std::ios::ate);
if(is.good()) {
return is;
}
is.close();
}
FATAL("File not found " << paths.back());
}();
const auto end = is.tellg();
is.seekg(0, std::ios::beg);
resize(end - is.tellg());
is.read(data(), size());
is.close();
}
};
}
\ No newline at end of file
#pragma once
#include "data.hpp"
#include "PacketView.hpp"
#include <Tracy.hpp>
namespace net {
......@@ -37,11 +39,12 @@ public:
}
~Client() {
LOG_D("Leaving server");
enet_peer_disconnect_now(peer, 1);
enet_peer_disconnect_now(peer, (enet_uint32)disconnect_reason::QUIT);
enet_host_destroy(host);
}
void pull(int delay = 0) {
template<typename D, typename R>
void pull(R onData, D onDisconnect, int delay = 0) {
ENetEvent event;
while(enet_host_service(host, &event, delay) > 0) {
switch(event.type) {
......@@ -50,7 +53,9 @@ public:
break;
case ENET_EVENT_TYPE_DISCONNECT:
onDisconnect(event.data);
LOG_D("Client disconnected with reason " << event.data);
ready = false;
onDisconnect((disconnect_reason)event.data);
break;
case ENET_EVENT_TYPE_RECEIVE: {
......@@ -59,7 +64,7 @@ public:
break;
}
const server_packet_type type = static_cast<server_packet_type>(*event.packet->data);
if(type < server_packet_type::CHUNK) {
if(type < server_packet_type::BROADCASTED) {
if(event.packet->dataLength < sizeof(server_packet_type) + sizeof(salt)) {
LOG_D("Wrong salted packet size");
break;
......@@ -76,14 +81,14 @@ public:
}
salt_t l;
memcpy(&l, event.packet->data + sizeof(server_packet_type) + sizeof(salt), sizeof(l));
PacketReader(event.packet).read(l);
salt ^= l;
LOG_D("Handshake done");
ready = true;
break;
}
onData(event.channelID, event.packet);
onData(event.packet, (channel_type)event.channelID);
enet_packet_destroy(event.packet);
break;
}
......@@ -92,35 +97,29 @@ public:
break;
}
}
TracyPlot("CltNetUp", (int64_t)host->outgoingBandwidth);
TracyPlot("CltNetDown", (int64_t)host->incomingBandwidth);
TracyPlot("CltNetRTT", (int64_t)peer->roundTripTime);
}
virtual void onDisconnect(enet_uint32 reason) {
LOG_D("Client disconnected with reason " << reason);
ready = false;
bool send(packet_t* packet, channel_type channel) {
return enet_peer_send(peer, (enet_uint8)channel, packet) == 0;
}
virtual void onData(enet_uint8 channel, ENetPacket* packet) {
LOG_D("Data from server " << packet->dataLength << " on " << channel);
}
bool send(client_packet_type type, ENetPacket* packet, channel_type channel) {
assert(packet->dataLength >= HEADER_SIZE);
if(!ready) {
LOG_W("Contacting server before handshake");
enet_packet_destroy(packet);
return false;
}
memcpy(packet->data, &type, sizeof(client_packet_type));
memcpy(packet->data + sizeof(client_packet_type), &salt, sizeof(salt));
return enet_peer_send(peer, (enet_uint8)(channel), packet) == 0;
}
bool send(client_packet_type type, const void* data, size_t size, channel_type channel, std::optional<enet_uint32> flags = {}) {
auto packet = enet_packet_create(NULL, HEADER_SIZE + size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0));
memcpy(packet->data + HEADER_SIZE, data, size);
return send(type, packet, channel);
bool send(client_packet_type type, const void *data, size_t size, channel_type channel, std::optional<enet_uint32> flags = {}) {
return send(makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0), salt).get(), channel);
}
template<typename D>
bool send(client_packet_type type, const D& payload, channel_type channel, std::optional<enet_uint32> flags = {}) {
return send(type, &payload, sizeof(payload), channel, flags);
bool send(client_packet_type type, const D& data, channel_type channel, std::optional<enet_uint32> flags = {}) {
return send(type, &data, sizeof(data), channel, flags);
}
static PacketWriter makePacket(client_packet_type type, const void* data, size_t size, enet_uint32 flags, salt_t salt) {
auto packet = PacketWriter(sizeof(client_packet_type) + sizeof(salt_t) + size, flags);
packet.write(type);
packet.write(salt);
if (data != nullptr)
packet.write(data, size);
return packet;
}
constexpr bool isReady() const { return ready; }
......
#pragma once
#include "data.hpp"
struct vec_istream: std::streambuf {
vec_istream(std::vector<char> &vec) {
this->setg(&vec[0], &vec[0], &vec[0] + vec.size());
}
};
namespace net {
class PacketWriter final {
public:
PacketWriter(size_t size, enet_uint32 flags): p(enet_packet_create(NULL, size, flags)) { }
void write(const void* data, size_t size) {
assert(i + size <= p->dataLength);
memcpy(p->data + i, data, size);
i += size;
}
template<typename D>
void write(const D& d) {
write(&d, sizeof(d));
}
bool isFull() const {
return i >= p->dataLength;
}
packet_t *get() { return p; }
private:
packet_t *p;
size_t i = 0;
};
struct data_ref {
void* d;
size_t s;
void *data() { return d; }
const void *data() const { return d; }
size_t size() const { return s; }
};
class PacketReader final {
public:
PacketReader(packet_t* ptr, size_t offset): p(ptr), i(offset) { }
PacketReader(packet_t* ptr, bool broadcasted = false):
PacketReader(ptr, sizeof(enet_uint8) + (broadcasted ? 0 : sizeof(salt_t))) { }
template<typename D>
const D* read() {
if (i + sizeof(D) > p->dataLength)
return nullptr;
auto ptr = (const D *)(p->data + i);
i += sizeof(D);
return ptr;
}
template<typename D>
bool read(D& out) {
const auto ptr = read<D>();
if (ptr == nullptr)
return false;
out = *ptr;
return true;
}
bool skip(size_t size) {
i += size;
return i < p->dataLength;
}
data_ref remaning() {
auto res = data_ref{p->data + i, p->dataLength - i};
i = p->dataLength;
return res;
}
bool isFull() const {
return i >= p->dataLength;
}
packet_t *get() { return p; }
private:
packet_t *p;
size_t i = 0;
};
}
\ No newline at end of file
#pragma once
#include "data.hpp"
#include "PacketView.hpp"
namespace net {
......@@ -25,7 +26,8 @@ public:
enet_host_destroy(host);
}
void pull(int delay = 0) {
template<typename C, typename D, typename R>
void pull(C onConnect, D onDisconnect, R onData, int delay = 0) {
ENetEvent event;
while(enet_host_service(host, &event, delay) > 0) {
switch(event.type) {
......@@ -34,23 +36,11 @@ public:
break;
case ENET_EVENT_TYPE_DISCONNECT:
onDisconnect(event.peer, event.data);
onDisconnect(event.peer, (disconnect_reason)event.data);
break;
case ENET_EVENT_TYPE_RECEIVE: {
if(event.packet->dataLength < sizeof(client_packet_type) + sizeof(salt_t)) {
LOG_D("Empty packet from " << event.peer->address.host << ":" << event.peer->address.port);
break;
}
if (memcmp(event.peer->data, event.packet->data + sizeof(client_packet_type), sizeof(salt_t)) != 0) {
LOG_D("Wrong salt from " << event.peer->address);
salt_t l;
memcpy(&l, event.packet->data + sizeof(client_packet_type), sizeof(l));
memcpy(&l, event.peer->data, sizeof(l));
enet_peer_disconnect(event.peer, 15);
break;
}
onData(event.peer, event.packet, event.channelID);
onData(event.peer, event.packet, (channel_type)event.channelID);
enet_packet_destroy(event.packet);
break;
}
......