Single player server
This commit is contained in:
commit
c760edb0a4
7
TODO.md
7
TODO.md
|
@ -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() { }
|
||||
};
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
#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{}; }
|
|
@ -0,0 +1,182 @@
|
|||
#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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <zstd.h>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include "../utils/logger.hpp"
|
||||
|
||||
namespace zstd {
|
||||
|
||||
struct read_ctx {
|
||||
~read_ctx() {
|
||||
ZSTD_freeDCtx(ctx);
|
||||
}
|
||||
ZSTD_DCtx *ctx;
|
||||
ZSTD_DDict *dict;
|
||||
};
|
||||
struct write_ctx {
|
||||
~write_ctx() {
|
||||
ZSTD_freeCCtx(ctx);
|
||||
}
|
||||
ZSTD_CCtx *ctx;
|
||||
ZSTD_CDict *dict;
|
||||
};
|
||||
|
||||
class dict_set {
|
||||
public:
|
||||
dict_set(const void *data, size_t size) { load(data, size); }
|
||||
dict_set(const std::vector<std::string>& paths) {
|
||||
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("Missing dict " << paths.back());
|
||||
}();
|
||||
const auto end = is.tellg();
|
||||
is.seekg(0, std::ios::beg);
|
||||
std::vector<char> dict(end - is.tellg());
|
||||
is.read(dict.data(), dict.size());
|
||||
is.close();
|
||||
load(dict.data(), dict.size());
|
||||
}
|
||||
~dict_set() {
|
||||
ZSTD_freeCDict(c);
|
||||
ZSTD_freeDDict(d);
|
||||
}
|
||||
|
||||
read_ctx make_reader() const {
|
||||
return read_ctx{ZSTD_createDCtx(), d};
|
||||
}
|
||||
write_ctx make_writer() const {
|
||||
return write_ctx{ZSTD_createCCtx(), c};
|
||||
}
|
||||
private:
|
||||
void load(const void* data, size_t size) {
|
||||
c = ZSTD_createCDict(data, size, ZSTD_CLEVEL_DEFAULT);
|
||||
assert(c != NULL);
|
||||
d = ZSTD_createDDict(data, size);
|
||||
assert(d != NULL);
|
||||
}
|
||||
|
||||
ZSTD_CDict *c;
|
||||
ZSTD_DDict *d;
|
||||
};
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
#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; }
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
#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;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#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;
|
||||
}
|
||||
|
@ -59,46 +49,75 @@ public:
|
|||
break;
|
||||
}
|
||||
}
|
||||
TracyPlot("SrvNetUp", (int64_t)host->outgoingBandwidth);
|
||||
TracyPlot("SrvNetDown", (int64_t)host->incomingBandwidth);
|
||||
}
|
||||
|
||||
virtual void onConnect(ENetPeer* peer, enet_uint32 salt) {
|
||||
LOG_D("Client connect from " << peer->address);
|
||||
const salt_t rnd = std::rand();
|
||||
peer->data = enet_malloc(sizeof(salt_t));
|
||||
memcpy(peer->data, &salt, sizeof(salt_t));
|
||||
const auto packet = enet_packet_create(NULL, sizeof(server_packet_type) + sizeof(salt_t) * 2, ENET_PACKET_FLAG_RELIABLE);
|
||||
memcpy(packet->data + sizeof(server_packet_type) + sizeof(salt_t), &rnd, sizeof(salt_t));
|
||||
sendTo(peer, server_packet_type::CHALLENGE, packet, net::channel_type::RELIABLE);
|
||||
salt ^= rnd;
|
||||
memcpy(peer->data, &salt, sizeof(salt_t));
|
||||
}
|
||||
virtual void onDisconnect(ENetPeer* peer, enet_uint32 reason) {
|
||||
LOG_D("Client disconnect from " << peer->address << " with " << reason);
|
||||
enet_free(peer->data);
|
||||
}
|
||||
virtual void onData(ENetPeer* peer, ENetPacket*, enet_uint8) {
|
||||
LOG_D("Data from " << peer->address);
|
||||
void disconnect(peer_t *peer, disconnect_reason reason) const {
|
||||
enet_peer_disconnect(peer, (enet_uint32)reason);
|
||||
}
|
||||
|
||||
bool sendTo(ENetPeer* peer, server_packet_type type, ENetPacket* packet, channel_type channel) {
|
||||
assert(type < server_packet_type::CHUNK);
|
||||
assert(packet->dataLength >= sizeof(server_packet_type) + sizeof(salt_t));
|
||||
if(peer->data == NULL) {
|
||||
template<typename D>
|
||||
static D* GetPeerData(peer_t* peer) { return (D*)peer->data; }
|
||||
|
||||
/// 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 = {}) {
|
||||
const auto salt = GetPeerData<salt_t>(peer);
|
||||
if(salt == NULL) {
|
||||
LOG_W("Contacting " << peer->address << " before handshake");
|
||||
return false;
|
||||
}
|
||||
memcpy(packet->data, &type, sizeof(server_packet_type));
|
||||
memcpy(packet->data + sizeof(server_packet_type), peer->data, sizeof(salt_t));
|
||||
return enet_peer_send(peer, (enet_uint8)channel, packet) == 0;
|
||||
return send(peer, makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0), *salt).get(), channel);
|
||||
}
|
||||
void broadcast(server_packet_type type, ENetPacket* packet, channel_type channel) {
|
||||
assert(type >= server_packet_type::CHUNK);
|
||||
assert(packet->dataLength >= sizeof(server_packet_type));
|
||||
memcpy(packet->data, &type, sizeof(server_packet_type));
|
||||
enet_host_broadcast(host, (enet_uint8)channel, packet);
|
||||
template<typename D>
|
||||
bool sendTo(peer_t* peer, server_packet_type type, const D& data, channel_type channel, std::optional<enet_uint32> flags = {}) {
|
||||
return sendTo(peer, type, &data, sizeof(data), channel, flags);
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Send unsalt to single peer
|
||||
bool send(peer_t* peer, packet_t* packet, channel_type channel) {
|
||||
return enet_peer_send(peer, (enet_uint8)channel, packet) == 0;
|
||||
}
|
||||
bool send(peer_t* peer, server_packet_type type, const void *data, size_t size, channel_type channel, std::optional<enet_uint32> flags = {}) {
|
||||
return send(peer, makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0)).get(), channel);
|
||||
}
|
||||
template<typename D>
|
||||
bool send(peer_t* peer, server_packet_type type, const D& data, channel_type channel, std::optional<enet_uint32> flags = {}) {
|
||||
return send(peer, type, &data, sizeof(data), channel, flags);
|
||||
}
|
||||
|
||||
/// Send to all connected peers
|
||||
void broadcast(packet_t* packet, channel_type channel) {
|
||||
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 = {}) {
|
||||
broadcast(makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0)).get(), channel);
|
||||
}
|
||||
template<typename D>
|
||||
void broadcast(server_packet_type type, const D& data, channel_type channel, std::optional<enet_uint32> flags = {}) {
|
||||
broadcast(type, &data, sizeof(data), channel, flags);
|
||||
}
|
||||
|
||||
static PacketWriter makePacket(server_packet_type type, const void* data, size_t size, enet_uint32 flags) {
|
||||
assert(type >= server_packet_type::BROADCASTED);
|
||||
auto packet = PacketWriter(sizeof(server_packet_type) + size, flags);
|
||||
packet.write(type);
|
||||
if (data != nullptr)
|
||||
packet.write(data, size);
|
||||
return packet;
|
||||
}
|
||||
static PacketWriter makePacket(server_packet_type type, const void* data, size_t size, enet_uint32 flags, salt_t salt) {
|
||||
assert(type < server_packet_type::BROADCASTED);
|
||||
auto packet = PacketWriter(sizeof(server_packet_type) + sizeof(salt_t) + size, flags);
|
||||
packet.write(type);
|
||||
packet.write(salt);
|
||||
if (data != nullptr)
|
||||
packet.write(data, size);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private:
|
||||
ENetHost *host;
|
||||
};
|
||||
}
|
|
@ -13,22 +13,42 @@ enum class channel_type: enet_uint8 {
|
|||
NOTIFY = 1,
|
||||
};
|
||||
|
||||
enum class disconnect_reason: enet_uint32 {
|
||||
UNEXPECTED = 0,
|
||||
QUIT = 1,
|
||||
WRONG_SALT = 15,
|
||||
};
|
||||
|
||||
using salt_t = enet_uint32;
|
||||
using peer_t = ENetPeer;
|
||||
using packet_t = ENetPacket;
|
||||
|
||||
enum class server_packet_type: enet_uint8 {
|
||||
/// Get server salt c0 realable
|
||||
PERSONAL = 0,
|
||||
/// Get server salt
|
||||
/// realable
|
||||
CHALLENGE = 0,
|
||||
|
||||
/// Full chunk update c0 realable
|
||||
CHUNK = 16,
|
||||
/// Chunk changed c0 realable
|
||||
EDITS = 17,
|
||||
BROADCASTED = 16,
|
||||
/// List all areas
|
||||
/// {area_id, world::Area::params}[] realable
|
||||
AREAS = 16,
|
||||
/// Full chunk update
|
||||
/// {area_<chunk_pos>, zstd<chunk rle>} realable
|
||||
CHUNK = 17,
|
||||
/// Chunk changes
|
||||
/// {area_id, {chunk_pos, short(count), Chunk::Edit[]}[]} notify
|
||||
/// FIXME: to big !!! MAYBE: compress
|
||||
EDITS = 18,
|
||||
|
||||
/// World compression dictionary
|
||||
/// zstd dict realable
|
||||
COMPRESSION = 24,
|
||||
};
|
||||
enum class client_packet_type: enet_uint8 {
|
||||
/// Interact with voxels
|
||||
/// TODO: reliable
|
||||
EDIT = 0,
|
||||
/// actions::FillCube realable
|
||||
FILL_CUBE = 0,
|
||||
|
||||
/// Position update
|
||||
/// voxel_pos notify
|
||||
|
@ -66,16 +86,6 @@ inline std::ostream &operator<<(std::ostream &os, const ENetAddress &addr) {
|
|||
return os;
|
||||
}
|
||||
|
||||
template<typename D>
|
||||
bool read(const ENetPacket* packet, D& out, bool broadcasted = false) {
|
||||
const auto header = sizeof(enet_uint8) + (broadcasted ? 0 : sizeof(salt_t));
|
||||
if (packet->dataLength < header + sizeof(out))
|
||||
return false;
|
||||
|
||||
memcpy(&out, packet->data + header, sizeof(out));
|
||||
return true;
|
||||
}
|
||||
|
||||
void inline Setup() {
|
||||
if(enet_initialize() != 0) {
|
||||
FATAL("Enet initialization failed");
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
#pragma once
|
||||
|
||||
#include <zstd.h>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include "logger.hpp"
|
||||
|
||||
namespace zstd {
|
||||
|
||||
/// Decompressor with ref to dictionnary
|
||||
class read_ctx {
|
||||
public:
|
||||
read_ctx(ZSTD_DDict *dict): ctx(ZSTD_createDCtx()), dict(dict) { }
|
||||
~read_ctx() {
|
||||
ZSTD_freeDCtx(ctx);
|
||||
}
|
||||
|
||||
/// Extract in to out (error message on failure)
|
||||
template<typename I, typename O>
|
||||
std::optional<const char*> decompress(const I& in, O& out) const {
|
||||
const auto maxSize = ZSTD_getFrameContentSize(in.data(), in.size());
|
||||
out.resize(maxSize);
|
||||
const auto actualSize = ZSTD_decompress_usingDDict(ctx, out.data(), out.size(), in.data(), in.size(), dict);
|
||||
if(ZSTD_isError(actualSize)) {
|
||||
return ZSTD_getErrorName(actualSize);
|
||||
}
|
||||
out.resize(actualSize);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
protected:
|
||||
ZSTD_DCtx *ctx;
|
||||
ZSTD_DDict *dict;
|
||||
};
|
||||
|
||||
/// Decompressor with builtin dictionnary
|
||||
class read_dict_ctx: public read_ctx {
|
||||
public:
|
||||
read_dict_ctx(const void* data, size_t size): read_ctx(ZSTD_createDDict(data, size)) {
|
||||
assert(dict != NULL);
|
||||
}
|
||||
~read_dict_ctx() {
|
||||
ZSTD_freeDDict(dict);
|
||||
}
|
||||
};
|
||||
|
||||
/// Compressor with ref to dictionnary
|
||||
class write_ctx {
|
||||
public:
|
||||
write_ctx(ZSTD_CDict *dict): ctx(ZSTD_createCCtx()), dict(dict) { }
|
||||
~write_ctx() {
|
||||
ZSTD_freeCCtx(ctx);
|
||||
}
|
||||
|
||||
/// Compress in to out (error message on failure)
|
||||
template<typename I, typename O>
|
||||
std::optional<const char*> compress(const I& in, O& out) const {
|
||||
const auto maxSize = ZSTD_compressBound(in.size());
|
||||
out.resize(maxSize);
|
||||
const auto actualSize = ZSTD_compress_usingCDict(ctx, out.data(), out.size(), in.data(), in.size(), dict);
|
||||
if(ZSTD_isError(actualSize)) {
|
||||
return ZSTD_getErrorName(actualSize);
|
||||
}
|
||||
out.resize(actualSize);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
protected:
|
||||
ZSTD_CCtx *ctx;
|
||||
ZSTD_CDict *dict;
|
||||
};
|
||||
|
||||
class dict_set {
|
||||
public:
|
||||
dict_set(const std::vector<char>& data) { load(data.data(), data.size()); }
|
||||
~dict_set() {
|
||||
ZSTD_freeCDict(c);
|
||||
ZSTD_freeDDict(d);
|
||||
}
|
||||
|
||||
read_ctx make_reader() const {
|
||||
return read_ctx(d);
|
||||
}
|
||||
write_ctx make_writer() const {
|
||||
return write_ctx(c);
|
||||
}
|
||||
private:
|
||||
void load(const void* data, size_t size) {
|
||||
c = ZSTD_createCDict(data, size, ZSTD_CLEVEL_DEFAULT);
|
||||
assert(c != NULL);
|
||||
d = ZSTD_createDDict(data, size);
|
||||
assert(d != NULL);
|
||||
}
|
||||
|
||||
ZSTD_CDict *c;
|
||||
ZSTD_DDict *d;
|
||||
};
|
||||
}
|
|
@ -49,8 +49,16 @@ namespace world {
|
|||
|
||||
virtual std::optional<double> getCurvature() const = 0;
|
||||
|
||||
private:
|
||||
struct params {
|
||||
voxel_pos center;
|
||||
int radius;
|
||||
std::optional<double> curvature;
|
||||
};
|
||||
|
||||
protected:
|
||||
area_pos center;
|
||||
|
||||
private:
|
||||
ChunkContainer chunks;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
using namespace world::client;
|
||||
|
||||
EdittableChunk::EdittableChunk(): world::Chunk() { }
|
||||
EdittableChunk::EdittableChunk(std::istream &is): world::Chunk(is) { }
|
||||
EdittableChunk::~EdittableChunk() { }
|
||||
|
||||
std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) {
|
||||
|
@ -37,7 +39,17 @@ void EdittableChunk::invalidate(ushort idx) {
|
|||
((!getNeighborIdx(idx, Face::Forward).has_value()) & Faces::Forward) |
|
||||
((!getNeighborIdx(idx, Face::Backward).has_value()) & Faces::Backward));
|
||||
}
|
||||
|
||||
void EdittableChunk::apply(const Edit& edit) {
|
||||
const auto prev = voxels[edit.idx];
|
||||
if(prev.value != edit.value.value) {
|
||||
voxels[edit.idx] = edit.value;
|
||||
if(edit.delay > 0) {
|
||||
edits.emplace_back<Edit>({edit.idx, prev, edit.delay});
|
||||
} else {
|
||||
invalidate(edit.idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<chunk_voxel_idx> EdittableChunk::getNeighborIdx(chunk_voxel_idx idx, Face dir) {
|
||||
switch (dir) {
|
||||
|
|
|
@ -7,6 +7,7 @@ using namespace geometry;
|
|||
namespace world::client {
|
||||
class EdittableChunk: public virtual world::Chunk {
|
||||
public:
|
||||
EdittableChunk(std::istream &is);
|
||||
virtual ~EdittableChunk();
|
||||
|
||||
/// Update voxels
|
||||
|
@ -20,9 +21,7 @@ namespace world::client {
|
|||
}
|
||||
void invalidate(chunk_voxel_idx idx);
|
||||
|
||||
virtual void apply(const Chunk::Edit &edit) {
|
||||
assert(false && "TODO");
|
||||
}
|
||||
void apply(const Chunk::Edit &edit);
|
||||
|
||||
/// Get pending changes
|
||||
const std::vector<Chunk::Edit> &getEdits() const { return edits; }
|
||||
|
@ -30,6 +29,8 @@ namespace world::client {
|
|||
static std::optional<chunk_voxel_idx> getNeighborIdx(chunk_voxel_idx idx, Face dir);
|
||||
|
||||
protected:
|
||||
EdittableChunk();
|
||||
|
||||
/// Temporary changes
|
||||
std::vector<Chunk::Edit> edits;
|
||||
/// Require update
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "Universe.hpp"
|
||||
|
||||
namespace world {
|
||||
|
||||
template<class Areas>
|
||||
Universe::ray_result Raycast(const geometry::Ray& ray, const Areas& areas) {
|
||||
//TODO: iterator
|
||||
//MAYBE: ray + offset to get float precision
|
||||
std::vector<voxel_pos> points;
|
||||
ray.grid(points);
|
||||
Universe::ray_result target;
|
||||
size_t dist = points.size();
|
||||
for(auto& area: areas) {
|
||||
if(ray.intersect(area.second->getBounding()) != geometry::IBox::ContainmentType::Disjoint) {
|
||||
const auto &offset = area.second->getOffset().as_voxel();
|
||||
const auto &chunks = area.second->getChunks();
|
||||
std::shared_ptr<world::Chunk> chunk = nullptr;
|
||||
chunk_pos chunk_vec(INT_MAX);
|
||||
for (size_t i = 0; i < dist; i++) {
|
||||
const auto pos = points[i] - offset;
|
||||
const chunk_pos cPos = glm::divide(pos);
|
||||
if(cPos != chunk_vec) {
|
||||
if (const auto it = chunks.find(cPos); it != chunks.end()) {
|
||||
chunk = it->second;
|
||||
chunk_vec = cPos;
|
||||
} else if(chunks.inRange(cPos)) {
|
||||
target.emplace<Universe::ray_out_of_range>(std::make_pair(area.first, cPos), offset);
|
||||
break;
|
||||
} else {
|
||||
chunk = nullptr;
|
||||
}
|
||||
}
|
||||
if(chunk != nullptr) {
|
||||
const auto voxel = chunk->getAt(glm::modulo(pos));
|
||||
if(voxel.is_solid()) {
|
||||
target.emplace<Universe::ray_target>(std::make_pair(area.first, pos), voxel, offset);
|
||||
dist = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,8 +17,8 @@ std::shared_ptr<Region> Area::getRegion(const std::string& folderPath, const are
|
|||
return unique->insert({pos.second, reg}).first->second;
|
||||
}
|
||||
|
||||
std::optional<double> Area::getCurvature() const {
|
||||
if (auto planet = std::get_if<world::generator::CubicPlanet::Params>(&generator_properties)) {
|
||||
std::optional<double> Area::GetCurvature(const world::generator::params& params) {
|
||||
if (auto planet = std::get_if<world::generator::CubicPlanet::Params>(¶ms)) {
|
||||
return planet->height * (1.1 + planet->surface_roughness);
|
||||
}
|
||||
return {};
|
||||
|
|
|
@ -8,7 +8,7 @@ using namespace libguarded;
|
|||
|
||||
namespace world::server {
|
||||
/// Area (aka big group of server::Chunk)
|
||||
struct Area: public world::Area {
|
||||
struct Area final: public world::Area {
|
||||
public:
|
||||
using regions_t = robin_hood::unordered_map<region_pos, std::shared_ptr<Region>>;
|
||||
|
||||
|
@ -27,7 +27,8 @@ namespace world::server {
|
|||
|
||||
inline params getParams() const { return params{getOffset().as_voxel(), getChunks().getRadius(), generator_properties}; }
|
||||
|
||||
std::optional<double> getCurvature() const override;
|
||||
std::optional<double> getCurvature() const override { return GetCurvature(generator_properties); }
|
||||
static std::optional<double> GetCurvature(const generator::params &);
|
||||
|
||||
private:
|
||||
shared_guarded<regions_t> regions;
|
||||
|
|
|
@ -47,9 +47,8 @@ void Chunk::set(ushort idx, const Voxel& val) {
|
|||
void Chunk::setAt(const chunk_voxel_pos& pos, const Voxel& val) {
|
||||
set(glm::toIdx(pos), val);
|
||||
}
|
||||
//TODO: extract delay+Edit to Universe
|
||||
std::optional<world::Item> Chunk::replace(chunk_voxel_idx idx, const Voxel& val, float delay) {
|
||||
std::optional<world::Item> Chunk::replace(chunk_voxel_idx idx, const Voxel& val, float) {
|
||||
const auto res = voxels[idx];
|
||||
set(idx, val);
|
||||
return {world::Item{res.density(), res.material()}}; //TODO: materials break table
|
||||
return {world::Item{res.density(), res.material()}};
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
namespace world::server {
|
||||
|
||||
// Server and Client merged chunk
|
||||
class SharedChunk: public Chunk, public world::client::EdittableChunk {
|
||||
class SharedChunk final: public Chunk, public world::client::EdittableChunk {
|
||||
public:
|
||||
SharedChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd): world::Chunk(), Chunk(pos, rnd), world::client::EdittableChunk() { }
|
||||
SharedChunk(std::istream &str, bool rle = RLE): world::Chunk(str, rle), Chunk(), world::client::EdittableChunk() { }
|
||||
|
@ -15,19 +15,13 @@ public:
|
|||
/// Break voxel
|
||||
std::optional<Item> replace(chunk_voxel_idx idx, const Voxel &val, float delay = 0) override {
|
||||
const auto res = voxels[idx];
|
||||
if (val.value != res.value) {
|
||||
set(idx, val);
|
||||
if(delay > 0) {
|
||||
edits.emplace_back<Edit>({idx, res, delay});
|
||||
} else {
|
||||
invalidate(idx);
|
||||
}
|
||||
set(idx, val);
|
||||
if(delay > 0) {
|
||||
edits.emplace_back<Edit>({idx, res, delay});
|
||||
} else {
|
||||
invalidate(idx);
|
||||
}
|
||||
return {Item{res.density(), res.material()}};
|
||||
}
|
||||
|
||||
void apply(const Edit &edit) {
|
||||
assert(false && "TODO:");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -7,7 +7,7 @@ namespace world::server {
|
|||
class SharedArea;
|
||||
|
||||
/// Server with data for LocalClientUniverse binding
|
||||
class SharedUniverse: public Universe {
|
||||
class SharedUniverse final: public Universe {
|
||||
public:
|
||||
//TODO: override area type
|
||||
//TODO: update edits
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
namespace world::server {
|
||||
/// Logic only server::Universe
|
||||
class StandaloneUniverse: public Universe {
|
||||
class StandaloneUniverse final: public Universe {
|
||||
public:
|
||||
StandaloneUniverse(const options &);
|
||||
};
|
||||
|
|
|
@ -6,19 +6,18 @@
|
|||
#include <random>
|
||||
|
||||
#include "Chunk.hpp"
|
||||
#include "../../core/world/raycast.hpp"
|
||||
#include "../../core/world/actions.hpp"
|
||||
#include "../../core/net/PacketView.hpp"
|
||||
|
||||
using namespace world::server;
|
||||
|
||||
const auto AREAS_FILE = "/areas.idx";
|
||||
|
||||
Universe::Universe(const Universe::options &options): net::Server(net::connection{"localhost", 4242}, 4), dicts({options.folderPath + "/zstd.dict", "content/zstd.dict"}) {
|
||||
Universe::Universe(const Universe::options &options): host(net::connection{"localhost", 4242}, 4),
|
||||
dict_content({options.folderPath + "/zstd.dict", "content/zstd.dict"}), dicts(dict_content), dict_write_ctx(dicts.make_writer()) {
|
||||
setOptions(options);
|
||||
folderPath = options.folderPath;
|
||||
struct vec_istream: std::streambuf {
|
||||
vec_istream(std::vector<char> &vec) {
|
||||
this->setg(&vec[0], &vec[0], &vec[0] + vec.size());
|
||||
}
|
||||
};
|
||||
running = true;
|
||||
|
||||
std::filesystem::create_directories(folderPath);
|
||||
|
@ -172,7 +171,8 @@ void Universe::saveAreas() const {
|
|||
|
||||
void Universe::update(float deltaTime) {
|
||||
ZoneScopedN("Universe");
|
||||
pull(100);
|
||||
|
||||
pullNetwork();
|
||||
|
||||
if(entities.at(PLAYER_ENTITY_ID).instances.empty())
|
||||
return;
|
||||
|
@ -183,15 +183,19 @@ void Universe::update(float deltaTime) {
|
|||
|
||||
if(chunkChange) {
|
||||
ZoneScopedN("Far");
|
||||
far_areas.extract([&](area_id id, Area::params params){
|
||||
bool extracted = false;
|
||||
far_areas.extract([&](area_id id, Area::params params) {
|
||||
if (const chunk_pos diff = glm::divide(pos - params.center);
|
||||
glm::length2(diff) > glm::pow2(loadDistance + params.radius))
|
||||
return false;
|
||||
|
||||
LOG_I("Load area " << id.index);
|
||||
areas.emplace(id, std::make_shared<Area>(params));
|
||||
extracted = true;
|
||||
return true;
|
||||
});
|
||||
if(extracted)
|
||||
broadcastAreas();
|
||||
}
|
||||
{ // Update alive areas
|
||||
ZoneScopedN("World");
|
||||
|
@ -310,26 +314,97 @@ void Universe::update(float deltaTime) {
|
|||
it->second->setChunks().emplace(loaded.first.second, loaded.second);
|
||||
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel());
|
||||
loadChunk(loaded.first, diff, it->second->getChunks());
|
||||
broadcastChunk(loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void Universe::onData(ENetPeer* peer, ENetPacket* packet, enet_uint8) {
|
||||
const auto type = static_cast<net::client_packet_type>(*packet->data);
|
||||
switch (type) {
|
||||
case net::client_packet_type::MOVE: {
|
||||
if(voxel_pos pos; net::read(packet, pos)) {
|
||||
LOG_I("Move " << pos.x << ',' << pos.y << ',' << pos.z);
|
||||
} else {
|
||||
LOG_D("Bad move");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
//LOG_D("Bad packet from " << peer->address);
|
||||
break;
|
||||
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;
|
||||
};
|
||||
|
||||
void Universe::pullNetwork() {
|
||||
using namespace net;
|
||||
host.pull(
|
||||
[&](peer_t *peer, salt_t salt) {
|
||||
LOG_D("Client connect from " << peer->address);
|
||||
net_client* client = new net_client(salt, entities.at(PLAYER_ENTITY_ID).instances.emplace(Entity::Instance{ }));
|
||||
peer->data = client;
|
||||
|
||||
const salt_t rnd = std::rand();
|
||||
host.sendTo<salt_t>(peer, server_packet_type::CHALLENGE, rnd, channel_type::RELIABLE);
|
||||
client->salt = salt ^ rnd;
|
||||
|
||||
host.send(peer, server_packet_type::COMPRESSION, dict_content.data(), dict_content.size(), channel_type::RELIABLE);
|
||||
broadcastAreas();
|
||||
},
|
||||
[](peer_t *peer, disconnect_reason reason) {
|
||||
LOG_D("Client disconnect from " << peer->address << " with " << (enet_uint32)reason);
|
||||
if (const auto data = Server::GetPeerData<net_client>(peer); data != nullptr)
|
||||
delete data;
|
||||
},
|
||||
[&](peer_t *peer, packet_t* packet, channel_type) {
|
||||
if(packet->dataLength < sizeof(client_packet_type) + sizeof(salt_t)) {
|
||||
LOG_D("Empty packet from " << peer->address);
|
||||
return;
|
||||
}
|
||||
if (memcmp(peer->data, packet->data + sizeof(client_packet_type), sizeof(salt_t)) != 0) {
|
||||
LOG_D("Wrong salt from " << peer->address);
|
||||
host.disconnect(peer, disconnect_reason::WRONG_SALT);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto type = static_cast<client_packet_type>(*packet->data);
|
||||
switch (type) {
|
||||
case client_packet_type::MOVE: {
|
||||
if(voxel_pos pos; !PacketReader(packet).read(pos) ||
|
||||
!movePlayer(Server::GetPeerData<net_client>(peer)->instanceId, pos)) {
|
||||
LOG_D("Bad move");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case client_packet_type::FILL_CUBE: {
|
||||
if(const auto fill = PacketReader(packet).read<world::action::FillCube>()) {
|
||||
//TODO: handle inventory
|
||||
setCube(fill->pos, fill->val, fill->radius);
|
||||
} else {
|
||||
LOG_D("Bad fill");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_D("Bad packet from " << peer->address);
|
||||
break;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
void Universe::broadcastAreas() {
|
||||
constexpr size_t ITEM_SIZE = sizeof(area_id) + sizeof(world::Area::params);
|
||||
|
||||
auto packet = net::Server::makePacket(net::server_packet_type::AREAS, NULL, ITEM_SIZE * areas.size(), ENET_PACKET_FLAG_RELIABLE);
|
||||
for(const auto& area: areas) {
|
||||
const auto params = area.second->getParams();
|
||||
packet.write(area.first);
|
||||
packet.write(world::Area::params{params.center, params.radius, area.second->getCurvature()});
|
||||
}
|
||||
|
||||
assert(packet.isFull());
|
||||
host.broadcast(packet.get(), net::channel_type::RELIABLE);
|
||||
}
|
||||
void Universe::broadcastChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &pair) {
|
||||
// MAYBE: limit chunks per update
|
||||
std::ostringstream out;
|
||||
pair.second->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);
|
||||
packet.write(buffer.data(), buffer.size());
|
||||
assert(packet.isFull());
|
||||
host.broadcast(packet.get(), net::channel_type::RELIABLE);
|
||||
}
|
||||
|
||||
void Universe::updateChunk(area_map::iterator &, world::ChunkContainer::iterator &, chunk_pos, float deltaTime) {}
|
||||
|
@ -341,43 +416,7 @@ void Universe::setOptions(const Universe::options& options) {
|
|||
}
|
||||
|
||||
Universe::ray_result Universe::raycast(const geometry::Ray &ray) const {
|
||||
//MAYBE: ray + offset to get float precision
|
||||
std::vector<voxel_pos> points;
|
||||
ray.grid(points);
|
||||
ray_result target;
|
||||
size_t dist = points.size();
|
||||
for(auto& area: areas) {
|
||||
if(ray.intersect(area.second->getBounding()) != geometry::IBox::ContainmentType::Disjoint) {
|
||||
const auto &offset = area.second->getOffset().as_voxel();
|
||||
const auto &chunks = area.second->getChunks();
|
||||
std::shared_ptr<world::Chunk> chunk = nullptr;
|
||||
chunk_pos chunk_vec(INT_MAX);
|
||||
for (size_t i = 0; i < dist; i++) {
|
||||
const auto pos = points[i] - offset;
|
||||
const chunk_pos cPos = glm::divide(pos);
|
||||
if(cPos != chunk_vec) {
|
||||
if (const auto it = chunks.find(cPos); it != chunks.end()) {
|
||||
chunk = it->second;
|
||||
chunk_vec = cPos;
|
||||
} else if(chunks.inRange(cPos)) {
|
||||
target.emplace<ray_out_of_range>(std::make_pair(area.first, cPos), offset);
|
||||
break;
|
||||
} else {
|
||||
chunk = nullptr;
|
||||
}
|
||||
}
|
||||
if(chunk != nullptr) {
|
||||
const auto voxel = chunk->getAt(glm::modulo(pos));
|
||||
if(voxel.is_solid()) {
|
||||
target.emplace<ray_target>(std::make_pair(area.first, pos), voxel, offset);
|
||||
dist = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
return Raycast(ray, areas);
|
||||
}
|
||||
|
||||
std::optional<world::Item> Universe::set(const area_<voxel_pos>& pos, const Voxel& val) {
|
||||
|
@ -393,7 +432,8 @@ std::optional<world::Item> Universe::set(const area_<voxel_pos>& pos, const Voxe
|
|||
world::ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val, int radius) {
|
||||
ItemList list;
|
||||
if(const auto it = areas.find(pos.first); it != areas.end()) {
|
||||
auto& chunks = it->second->setChunks();
|
||||
robin_hood::unordered_map<chunk_pos, std::vector<Chunk::Edit>> edits;
|
||||
auto &chunks = it->second->setChunks();
|
||||
for (int z = -radius; z <= radius; z++) {
|
||||
for (int y = -radius; y <= radius; y++) {
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
|
@ -401,9 +441,34 @@ world::ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val,
|
|||
const auto offset = voxel_pos(x, y, z);
|
||||
const auto split = glm::splitIdx(pos.second + offset);
|
||||
if(chunks.inRange(split.first))
|
||||
if(const auto chunk = it->second->setChunks().findInRange(split.first))
|
||||
list.add(std::dynamic_pointer_cast<Chunk>(chunk.value())->replace(split.second, val, glm::length2(offset) / radius * .05f));
|
||||
if(const auto chunk = it->second->setChunks().findInRange(split.first)) {
|
||||
auto ck = std::dynamic_pointer_cast<Chunk>(chunk.value());
|
||||
auto prev = ck->get(split.second);
|
||||
if(prev.value != val.value) {
|
||||
//TODO: apply break table
|
||||
//TODO: inventory
|
||||
const auto delay = glm::length2(offset) / radius * .05f;
|
||||
edits[split.first].push_back(Chunk::Edit{split.second, val, delay});
|
||||
ck->replace(split.second, val, delay);
|
||||
}
|
||||
}
|
||||
}}}
|
||||
size_t size = sizeof(area_id);
|
||||
for(const auto& part: edits) {
|
||||
size += sizeof(chunk_pos);
|
||||
size += sizeof(chunk_voxel_idx);
|
||||
size += sizeof(Chunk::Edit) * part.second.size();
|
||||
}
|
||||
auto packet = net::Server::makePacket(net::server_packet_type::EDITS, NULL, size, 0);
|
||||
packet.write(pos.first);
|
||||
for(const auto& part: edits) {
|
||||
packet.write(part.first);
|
||||
packet.write<chunk_voxel_idx>(part.second.size());
|
||||
packet.write(part.second.data(), part.second.size() * sizeof(Chunk::Edit));
|
||||
}
|
||||
|
||||
assert(packet.isFull());
|
||||
host.broadcast(packet.get(), net::channel_type::NOTIFY);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../../core/data/math.hpp"
|
||||
#include "../../core/data/safe_queue.hpp"
|
||||
#include "../../core/data/safe_priority_queue.hpp"
|
||||
#include "../../core/data/file.hpp"
|
||||
#include "../../core/net/Server.hpp"
|
||||
#include "Area.hpp"
|
||||
|
||||
|
@ -19,7 +20,7 @@ namespace world::server {
|
|||
class Chunk;
|
||||
|
||||
/// Whole universe container in abstract server
|
||||
class Universe: public world::Universe, net::Server {
|
||||
class Universe: public world::Universe {
|
||||
public:
|
||||
/// Server config
|
||||
struct options: world::Universe::options {
|
||||
|
@ -60,11 +61,17 @@ namespace world::server {
|
|||
/// Save all chunks (saveThread uses virtual calls)
|
||||
void saveAll(bool remove);
|
||||
|
||||
/// Handle networking requests
|
||||
void pullNetwork();
|
||||
void broadcastAreas();
|
||||
void broadcastChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &);
|
||||
|
||||
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
|
||||
|
||||
virtual std::shared_ptr<Chunk> createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const;
|
||||
virtual std::shared_ptr<Chunk> createChunk(std::istream &str) const;
|
||||
|
||||
|
||||
virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime);
|
||||
virtual void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &);
|
||||
|
||||
|
@ -92,8 +99,10 @@ namespace world::server {
|
|||
int keepDistance;
|
||||
std::string folderPath;
|
||||
|
||||
zstd::dict_set dicts;
|
||||
net::Server host;
|
||||
|
||||
void onData(ENetPeer *peer, ENetPacket *, enet_uint8) override;
|
||||
data::file_content dict_content;
|
||||
zstd::dict_set dicts;
|
||||
zstd::write_ctx dict_write_ctx;
|
||||
};
|
||||
}
|
|
@ -64,17 +64,14 @@ bool FileRegion::read(const region_chunk_pos& pos, const zstd::read_ctx& ctx, da
|
|||
if (it == index.end())
|
||||
return false;
|
||||
|
||||
auto in = std::make_unique<data>();
|
||||
in->resize(it->second.first);
|
||||
data in;
|
||||
in.resize(it->second.first);
|
||||
file.seekg(it->second.second);
|
||||
file.read(in->data(), in->size());
|
||||
file.read(in.data(), in.size());
|
||||
|
||||
const auto maxSize = ZSTD_getFrameContentSize(in->data(), in->size());
|
||||
out.resize(maxSize);
|
||||
const auto actualSize = ZSTD_decompress_usingDDict(ctx.ctx, out.data(), out.size(), in->data(), in->size(), ctx.dict);
|
||||
if(ZSTD_isError(actualSize)) {
|
||||
if (auto err = ctx.decompress(in, out)) {
|
||||
LOG_E("Corrupted region chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
|
||||
<< ZSTD_getErrorName(actualSize));
|
||||
<< err.value());
|
||||
#ifdef REMOVE_CORRUPTED
|
||||
LOG_W("Removing");
|
||||
index.erase(it);
|
||||
|
@ -83,21 +80,15 @@ bool FileRegion::read(const region_chunk_pos& pos, const zstd::read_ctx& ctx, da
|
|||
#endif
|
||||
return false;
|
||||
}
|
||||
out.resize(actualSize);
|
||||
return true;
|
||||
}
|
||||
void FileRegion::write(const region_chunk_pos& pos, const zstd::write_ctx& ctx, const std::string_view& in) {
|
||||
const auto maxSize = ZSTD_compressBound(in.size());
|
||||
auto buffer = std::make_unique<FileRegion::data>();
|
||||
buffer->resize(maxSize);
|
||||
|
||||
const auto actualSize = ZSTD_compress_usingCDict(ctx.ctx, buffer->data(), buffer->capacity(), in.data(), in.size(), ctx.dict);
|
||||
if (ZSTD_isError(actualSize)) {
|
||||
if (auto err = ctx.compress(in, *buffer)) {
|
||||
LOG_E("Corrupted chunk save: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
|
||||
<< ZSTD_getErrorName(actualSize));
|
||||
<< err.value());
|
||||
return;
|
||||
}
|
||||
buffer->resize(actualSize);
|
||||
save({{pos, std::move(buffer)}});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <shared_mutex>
|
||||
#include <fstream>
|
||||
#include "../../../core/world/forward.h"
|
||||
#include "../../../core/data/zctx.hpp"
|
||||
#include "../../../core/utils/zctx.hpp"
|
||||
#include "../../../core/data/math.hpp"
|
||||
|
||||
namespace world::server {
|
||||
|
|
|
@ -75,14 +75,9 @@ bool MemoryRegion::read(const region_chunk_pos& pos, const zstd::read_ctx& ctx,
|
|||
if (it == content.end())
|
||||
return false;
|
||||
|
||||
auto &in = it->second;
|
||||
|
||||
const auto maxSize = ZSTD_getFrameContentSize(in->data(), in->size());
|
||||
out.resize(maxSize);
|
||||
const auto actualSize = ZSTD_decompress_usingDDict(ctx.ctx, out.data(), out.size(), in->data(), in->size(), ctx.dict);
|
||||
if(ZSTD_isError(actualSize)) {
|
||||
if(auto err = ctx.decompress(*it->second, out)) {
|
||||
LOG_E("Corrupted region chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
|
||||
<< ZSTD_getErrorName(actualSize));
|
||||
<< err.value());
|
||||
#ifdef REMOVE_CORRUPTED
|
||||
LOG_W("Removing");
|
||||
lock.unlock();
|
||||
|
@ -94,21 +89,16 @@ bool MemoryRegion::read(const region_chunk_pos& pos, const zstd::read_ctx& ctx,
|
|||
#endif
|
||||
return false;
|
||||
}
|
||||
out.resize(actualSize);
|
||||
return true;
|
||||
}
|
||||
void MemoryRegion::write(const region_chunk_pos& pos, const zstd::write_ctx& ctx, const std::string_view& in) {
|
||||
const auto maxSize = ZSTD_compressBound(in.size());
|
||||
const auto buffer = new MemoryRegion::data();
|
||||
buffer->resize(maxSize);
|
||||
|
||||
const auto actualSize = ZSTD_compress_usingCDict(ctx.ctx, buffer->data(), buffer->capacity(), in.data(), in.size(), ctx.dict);
|
||||
if (ZSTD_isError(actualSize)) {
|
||||
if (auto err = ctx.compress(in, *buffer)) {
|
||||
LOG_E("Corrupted chunk save: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
|
||||
<< ZSTD_getErrorName(actualSize));
|
||||
<< err.value());
|
||||
return;
|
||||
}
|
||||
buffer->resize(actualSize);
|
||||
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <shared_mutex>
|
||||
#include <zstd.h>
|
||||
#include <fstream>
|
||||
#include "../../../core/world/forward.h"
|
||||
#include "../../../core/data/zctx.hpp"
|
||||
#include "../../../core/utils/zctx.hpp"
|
||||
#include "../../../core/data/math.hpp"
|
||||
|
||||
namespace world::server {
|
||||
|
|
Loading…
Reference in New Issue