1
0
Fork 0

Single player server

This commit is contained in:
May B. 2020-09-24 21:55:44 +02:00
commit c760edb0a4
29 changed files with 791 additions and 340 deletions

View File

@ -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

View File

@ -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;
};

View File

@ -4,7 +4,7 @@
namespace world::client {
class Chunk: public EdittableChunk {
class Chunk final: public EdittableChunk {
public:
Chunk(): world::Chunk(), EdittableChunk() { }
};

View File

@ -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{}; }

View File

@ -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);
}

View 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;
};
}

31
src/core/data/file.hpp Normal file
View File

@ -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();
}
};
}

View File

@ -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;
};
}

View File

@ -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; }

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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");

98
src/core/utils/zctx.hpp Normal file
View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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) {

View File

@ -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

View File

@ -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;
}
}

View File

@ -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>(&params)) {
return planet->height * (1.1 + planet->surface_roughness);
}
return {};

View File

@ -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;

View File

@ -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()}};
}

View File

@ -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:");
}
};
}

View File

@ -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

View File

@ -4,7 +4,7 @@
namespace world::server {
/// Logic only server::Universe
class StandaloneUniverse: public Universe {
class StandaloneUniverse final: public Universe {
public:
StandaloneUniverse(const options &);
};

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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)}});
}

View File

@ -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 {

View File

@ -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);

View File

@ -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 {