1
0
Fork 0

Client size edits

This commit is contained in:
May B. 2020-11-07 22:16:48 +01:00
parent 2bb02bef1f
commit fa482b37f3
14 changed files with 256 additions and 96 deletions

16
TODO.md
View File

@ -20,30 +20,30 @@
## Hello world ## Hello world
- [~] Map stream - [~] Map stream
- [ ] Modifications compression
- [ ] Local prediction - [ ] Local prediction
- [ ] Contouring service - [ ] Contouring service
- [~] Edit - [~] Edit
- [ ] Compression (raw)
- [x] Shape iterators - [x] Shape iterators
- [ ] Anchor - [ ] Anchor
- [x] Prevent suffocation - [x] Prevent suffocation
- [ ] Local prediction - [x] Local prediction
- [~] Occlusion Culling - [~] Occlusion Culling
- [ ] Iterator ray - [~] Iterator ray
- [ ] Cast from chunk center - [ ] Cast from chunk center
- [x] Transparency - [x] Transparency
- [~] Entities - [~] Entities
- [ ] Collide - [ ] Collide
- [ ] Get models - [ ] Get models
- [ ] Reduce compile unit count - [ ] Reduce compile unit count
- [ ] Review documentation - [~] Review documentation
- [ ] Clean kick - [ ] Clean kick
## Hello universe ## Hello universe
- [ ] CI build - [~] CI build
- CMake package - [ ] CMake package
- GitLab / Drone pipeline - [x] GitLab CI
- [ ] Universe - [ ] Universe
- [ ] Galaxy - [ ] Galaxy
- [ ] Rotation - [ ] Rotation
@ -79,7 +79,7 @@
- [ ] Slash screen - [ ] Slash screen
- [ ] Start/Pause menu - [ ] Start/Pause menu
- [x] QUIC protocol - [x] QUIC protocol
- [ ] 8 for 1 contouring problem - [~] 8 for 1 contouring problem
- [ ] Use in memory protocol (to replace server_handle) - [ ] Use in memory protocol (to replace server_handle)
- [ ] Octree - [ ] Octree
- [ ] Better Lod - [ ] Better Lod

View File

@ -66,6 +66,8 @@ public:
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance); world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
world.useAverages = config["world"]["use_averages"].value_or(world.useAverages); world.useAverages = config["world"]["use_averages"].value_or(world.useAverages);
world.trustMajorant = config["world"]["trust_majorant"].value_or(world.trustMajorant); world.trustMajorant = config["world"]["trust_majorant"].value_or(world.trustMajorant);
world.editPrediction = config["world"]["edit_prediction"].value_or(world.editPrediction);
world.editHandling = config["world"]["edit_handling"].value_or(world.editHandling);
voxel_density = config["world"]["voxel_density"].value_or(voxel_density); voxel_density = config["world"]["voxel_density"].value_or(voxel_density);
#ifndef STANDALONE #ifndef STANDALONE
@ -144,6 +146,8 @@ public:
{"keep_distance", world.keepDistance}, {"keep_distance", world.keepDistance},
{"use_averages", world.useAverages}, {"use_averages", world.useAverages},
{"trust_majorant", world.trustMajorant}, {"trust_majorant", world.trustMajorant},
{"edit_prediction", world.editPrediction},
{"edit_handling", world.editHandling},
{"voxel_density", voxel_density} {"voxel_density", voxel_density}
})); }));
if(connection.has_value()) { if(connection.has_value()) {

View File

@ -4,12 +4,48 @@
namespace world::client { namespace world::client {
class Chunk final: public EdittableChunk { class Chunk final: public EdittableChunk {
public: public:
Chunk(): world::Chunk(), EdittableChunk() { } Chunk(std::istream &is): world::Chunk(is) { }
}; /// Create from average
Chunk(Voxel val): EdittableChunk(), isAverage(true), isMajorant(val.swap()) { voxels.fill(Voxel(val.material(), val.density())); }
Chunk(): world::Chunk(), EdittableChunk() { }
/// Chunk full of air std::optional<Faces> update(float deltaTime, bool animate) override {
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>(); for (auto it = futureEdits.begin(); it != futureEdits.end();) {
it->second -= deltaTime;
if (it->second <= 0 && animate) {
invalidate(it->first.idx);
edits.emplace_back(it->first);
it = futureEdits.erase(it);
} else {
it++;
}
}
return EdittableChunk::update(deltaTime, animate);
}
constexpr bool isTrusted(bool allowMajorant) const { return !isAverage || (allowMajorant && isMajorant); }
void unsetMajorant() {
assert(isMajorant);
isMajorant = false;
}
/// Add future edit
void addFutureEdit(Chunk::Edit edit, float delay) { futureEdits.emplace_back(edit, delay); }
protected:
/// Is temporary average
const bool isAverage = false;
/// Is temporary full valued
bool isMajorant = false;
/// Temporary animated changes
/// MAYBE: sort by delay
std::vector<std::pair<Chunk::Edit, float>> futureEdits;
};
/// Chunk full of air
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>();
} }

View File

@ -5,6 +5,7 @@
#include "Area.hpp" #include "Area.hpp"
#include "../contouring/Abstract.hpp" #include "../contouring/Abstract.hpp"
#include "../../core/world/raycast.hpp" #include "../../core/world/raycast.hpp"
#include "../../core/world/iterators.hpp"
#include "../../core/net/io.hpp" #include "../../core/net/io.hpp"
#include "../../core/utils/logger.hpp" #include "../../core/utils/logger.hpp"
#include "Chunk.hpp" #include "Chunk.hpp"
@ -40,7 +41,7 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
if (glm::length2(diff - it_c->first) > glm::pow2(options.keepDistance)) { if (glm::length2(diff - it_c->first) > glm::pow2(options.keepDistance)) {
it_c = chunks.erase(it_c); it_c = chunks.erase(it_c);
} else { } else {
if(const auto neighbors = std::dynamic_pointer_cast<world::client::EdittableChunk>(it_c->second)->update(deltaTime, true /*MAYBE: random update*/)) { if(const auto neighbors = std::dynamic_pointer_cast<Chunk>(it_c->second)->update(deltaTime, true /*MAYBE: random update*/)) {
contouring->onUpdate(std::make_pair(area.first, it_c->first), diff, chunks, neighbors.value()); contouring->onUpdate(std::make_pair(area.first, it_c->first), diff, chunks, neighbors.value());
} }
++it_c; ++it_c;
@ -63,7 +64,7 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
const auto p = diff + chunk_pos(x, y, z); const auto p = diff + chunk_pos(x, y, z);
if (dist2 <= queryDistance * queryDistance && chunks.inRange(p)) { if (dist2 <= queryDistance * queryDistance && chunks.inRange(p)) {
if (auto it = chunks.find(p); it != chunks.end() && if (auto it = chunks.find(p); it != chunks.end() &&
std::dynamic_pointer_cast<world::client::EdittableChunk>(it->second)->isTrusted(options.trustMajorant)) std::dynamic_pointer_cast<Chunk>(it->second)->isTrusted(options.trustMajorant))
//TODO: config accept perfect average //TODO: config accept perfect average
{ {
contouring->onNotify(std::make_pair(area.first, p), diff, chunks); contouring->onNotify(std::make_pair(area.first, p), diff, chunks);
@ -73,7 +74,7 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
const auto rcPos = glm::split(p); const auto rcPos = glm::split(p);
if(auto it_r = cl_area->regionCache.find(rcPos.first); it_r != cl_area->regionCache.end()) { if(auto it_r = cl_area->regionCache.find(rcPos.first); it_r != cl_area->regionCache.end()) {
if (auto it_rc = it_r->second.find(rcPos.second); it_rc != it_r->second.end() && (it_rc->second.swap() || options.useAverages)) { if (auto it_rc = it_r->second.find(rcPos.second); it_rc != it_r->second.end() && (it_rc->second.swap() || options.useAverages)) {
auto ck = std::make_shared<world::client::EdittableChunk>(it_rc->second); auto ck = std::make_shared<Chunk>(it_rc->second);
ck->invalidate(geometry::Faces::All); ck->invalidate(geometry::Faces::All);
chunks.emplace(p, std::dynamic_pointer_cast<world::Chunk>(ck)); chunks.emplace(p, std::dynamic_pointer_cast<world::Chunk>(ck));
contouring->onNotify(std::make_pair(area.first, p), diff, chunks); contouring->onNotify(std::make_pair(area.first, p), diff, chunks);
@ -111,7 +112,6 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
packet.write(area.first); packet.write(area.first);
packet.write(missingChunks.data(), missingChunks.size() * sizeof(chunk_pos)); packet.write(missingChunks.data(), missingChunks.size() * sizeof(chunk_pos));
peer.send(packet.finish()); peer.send(packet.finish());
LOG("Query " << missingChunks.size());
} }
if(!missingRegions.empty()) { if(!missingRegions.empty()) {
auto packet = net::PacketWriter(net::client_packet_type::MISSING_REGIONS, sizeof(area_id) + missingRegions.size() * sizeof(region_pos)); auto packet = net::PacketWriter(net::client_packet_type::MISSING_REGIONS, sizeof(area_id) + missingRegions.size() * sizeof(region_pos));
@ -151,8 +151,19 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
break; break;
} }
case server_packet_type::CAPABILITIES: case server_packet_type::CAPABILITIES: {
return packet.read(serverDistance); packet.read(serverDistance);
auto predictable = false;
packet.read(predictable);
if (!predictable) {
options.editHandling = false;
options.editPrediction = false;
}
auto packet = PacketWriter(client_packet_type::CAPABILITIES, sizeof(bool));
packet.write(options.editHandling);
peer.send(packet.finish());
break;
}
case server_packet_type::COMPRESSION: { case server_packet_type::COMPRESSION: {
const auto remain = packet.readAll(); const auto remain = packet.readAll();
@ -222,7 +233,6 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
if (voxel.swap()) if (voxel.swap())
full++; full++;
} }
LOG(full << "/" << total);
break; break;
} }
@ -257,7 +267,7 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
} }
data::vec_istream idata(buffer); data::vec_istream idata(buffer);
std::istream iss(&idata); std::istream iss(&idata);
auto ck = std::make_shared<world::client::EdittableChunk>(iss); auto ck = std::make_shared<Chunk>(iss);
ck->invalidate(geometry::Faces::All); ck->invalidate(geometry::Faces::All);
auto ptr = std::dynamic_pointer_cast<world::Chunk>(ck); auto ptr = std::dynamic_pointer_cast<world::Chunk>(ck);
auto &chunks = it->second->setChunks(); auto &chunks = it->second->setChunks();
@ -271,6 +281,38 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
case server_packet_type::EDITS: { case server_packet_type::EDITS: {
ZoneScopedN("Edits"); ZoneScopedN("Edits");
if (!options.editHandling) {
LOG_W("Unhandled edit type");
break;
}
const auto fill = packet.read<action::FillShape>();
if (!fill)
break;
if(const auto it = areas.find(fill->pos.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
auto iterator = world::iterator::Get(fill->shape, fill->radius);
world::iterator::pair point;
while (iterator->next(point)) {
const voxel_pos offset = point.first;
const auto split = glm::splitIdx(fill->pos.second + offset);
if(chunks.inRange(split.first)) {
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);
const auto next = prev.filled(fill->val, point.second);
const auto delay = glm::length2(offset) / fill->radius * .05f;
ck->apply(Chunk::Edit{split.second, next, delay});
}
}
}
}
break;
}
case server_packet_type::RAW_EDITS: {
ZoneScopedN("Raw Edits");
area_id id; area_id id;
if(!packet.read(id)) if(!packet.read(id))
break; break;
@ -287,7 +329,7 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
chunk_voxel_idx count = 0; chunk_voxel_idx count = 0;
packet.read(count); packet.read(count);
if (auto ptr = it->second->setChunks().findInRange(pos)) { if (auto ptr = it->second->setChunks().findInRange(pos)) {
auto chunk = std::dynamic_pointer_cast<world::client::EdittableChunk>(ptr.value()); auto chunk = std::dynamic_pointer_cast<Chunk>(ptr.value());
for (auto i = 0; i < count && !packet.isFull(); i++) { for (auto i = 0; i < count && !packet.isFull(); i++) {
Chunk::Edit edit; Chunk::Edit edit;
packet.read(edit); packet.read(edit);
@ -351,6 +393,30 @@ void DistantUniverse::emit(const action::packet &action) {
peer.send(net::PacketWriter::Of(net::client_packet_type::MESSAGE, message->text.data(), message->text.size())); peer.send(net::PacketWriter::Of(net::client_packet_type::MESSAGE, message->text.data(), message->text.size()));
} else if(const auto fill = std::get_if<action::FillShape>(&action)) { } else if(const auto fill = std::get_if<action::FillShape>(&action)) {
peer.send(net::PacketWriter::Of(net::client_packet_type::FILL_SHAPE, *fill)); peer.send(net::PacketWriter::Of(net::client_packet_type::FILL_SHAPE, *fill));
if (options.editPrediction) {
ZoneScopedN("Fill");
const auto keepDelay = 10 + (peer.getRTT() / 20000.f); // 10s + 50RTT
if(const auto it = areas.find(fill->pos.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
auto iterator = world::iterator::Get(fill->shape, fill->radius);
world::iterator::pair point;
while (iterator->next(point)) {
const voxel_pos offset = point.first;
const auto split = glm::splitIdx(fill->pos.second + offset);
if(chunks.inRange(split.first)) {
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);
const auto next = prev.filled(fill->val, point.second);
if(prev.value != next.value) {
const auto delay = glm::length2(offset) / fill->radius * .05f;
ck->addFutureEdit(Chunk::Edit{split.second, next, keepDelay - delay * 2}, delay);
}
}
}
}
}
}
} else { } else {
LOG_W("Bad action " << action.index()); LOG_W("Bad action " << action.index());
} }

View File

@ -25,6 +25,10 @@ namespace world::client {
/// Dont query chunks with absolute majorant average /// Dont query chunks with absolute majorant average
/// Avoid querying whole space but may cause missed edits in those chunks /// Avoid querying whole space but may cause missed edits in those chunks
bool trustMajorant = true; bool trustMajorant = true;
/// Apply edits locally while sending
bool editPrediction = true;
/// Get only edits commands and compute locally
bool editHandling = true;
}; };
struct connection: net::address { struct connection: net::address {
connection(): net::address{"localhost", 4242} { } connection(): net::address{"localhost", 4242} { }

View File

@ -321,6 +321,9 @@ void Connection::release(uint16_t reason) {
} }
} }
uint64_t Connection::getRTT() {
return cnx != nullptr ? picoquic_get_rtt(cnx) : INT64_MAX;
}
uint16_t Connection::getErrorCode(bool is_app) { uint16_t Connection::getErrorCode(bool is_app) {
return cnx != nullptr ? (is_app ? cnx->remote_application_error : cnx->remote_error) : 42; return cnx != nullptr ? (is_app ? cnx->remote_application_error : cnx->remote_error) : 42;
} }

View File

@ -103,7 +103,10 @@ public:
void setCallback(picoquic_stream_data_cb_fn cb_fn, void *ctx); void setCallback(picoquic_stream_data_cb_fn cb_fn, void *ctx);
/// Average roundtrip time in microseconds
uint64_t getRTT();
uint16_t getErrorCode(bool is_app); uint16_t getErrorCode(bool is_app);
/// Ip address and port
std::string getAddress(); std::string getAddress();
/// Send reliable data /// Send reliable data

View File

@ -45,30 +45,33 @@ enum class server_packet_type: uint8_t {
/// {area_<chunk_pos>, zstd<chunk rle>} /// {area_<chunk_pos>, zstd<chunk rle>}
/// empty: all sent /// empty: all sent
CHUNK = 18, CHUNK = 18,
/// Chunk changes /// Uncompressed chunk changes
/// {area_id, {chunk_pos, ushort(count), Chunk::Edit[]}[]} notify /// {area_id, {chunk_pos, ushort(count), Chunk::Edit[]}[]}
/// MAYBE: compress RAW_EDITS = 19,
EDITS = 19, /// Chunk changes instruction
/// action::FillShape
EDITS = 20,
/// Declare entities types /// Declare entities types
/// {size_t(index), vec3(size), vec3(scale)} /// {size_t(index), vec3(size), vec3(scale)}
/// TODO: zstd<chunk rle> /// TODO: zstd<chunk rle>
ENTITY_TYPES = 20, ENTITY_TYPES = 32,
/// Update entities instances position and velocity /// Update entities instances position and velocity
/// {size_t(entity), size_t(count), {size_t(index), ifvec3(pos), vec3(velocity)}[]}[] /// {size_t(entity), size_t(count), {size_t(index), ifvec3(pos), vec3(velocity)}[]}[]
ENTITIES = 21, ENTITIES = 33,
/// World compression dictionary /// World compression dictionary
/// zstd dict /// zstd dict
COMPRESSION = 24, COMPRESSION = 64,
/// Server capabilities /// Server capabilities
/// ushort(loadDistance), MAYBE: more /// ushort(loadDistance), bool(predictable)
CAPABILITIES = 25, //MAYBE: use uint8_t flags
CAPABILITIES = 65,
/// Public chat message /// Public chat message
/// char[] (not null terminated) /// char[] (not null terminated)
MESSAGE = 29, MESSAGE = 129,
}; };
/// Packets from client to server /// Packets from client to server
enum class client_packet_type: uint8_t { enum class client_packet_type: uint8_t {
@ -91,6 +94,11 @@ enum class client_packet_type: uint8_t {
/// Position update (unreliable) /// Position update (unreliable)
/// (sequence)uint64_t voxel_pos /// (sequence)uint64_t voxel_pos
MOVE = 16, MOVE = 16,
/// Client capabilities
/// bool(edit_handling)
//MAYBE: use uint8_t flags
CAPABILITIES = 65,
}; };
constexpr auto MAX_PENDING_CHUNK_COUNT = 256; constexpr auto MAX_PENDING_CHUNK_COUNT = 256;

View File

@ -7,8 +7,6 @@
using namespace world::client; using namespace world::client;
EdittableChunk::EdittableChunk(): world::Chunk() { } EdittableChunk::EdittableChunk(): world::Chunk() { }
EdittableChunk::EdittableChunk(std::istream &is): world::Chunk(is) { }
EdittableChunk::EdittableChunk(Voxel val): world::Chunk(), isAverage(true), isMajorant(val.swap()) { voxels.fill(Voxel(val.material(), val.density())); }
EdittableChunk::~EdittableChunk() { } EdittableChunk::~EdittableChunk() { }
std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) { std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) {

View File

@ -7,9 +7,6 @@ using namespace geometry;
namespace world::client { namespace world::client {
class EdittableChunk: public virtual world::Chunk { class EdittableChunk: public virtual world::Chunk {
public: public:
EdittableChunk(std::istream &is);
/// Create from average
EdittableChunk(Voxel val);
virtual ~EdittableChunk(); virtual ~EdittableChunk();
/// Update voxels /// Update voxels
@ -30,21 +27,11 @@ namespace world::client {
static std::optional<chunk_voxel_idx> getNeighborIdx(chunk_voxel_idx idx, Face dir); static std::optional<chunk_voxel_idx> getNeighborIdx(chunk_voxel_idx idx, Face dir);
constexpr bool isTrusted(bool allowMajorant) const { return !isAverage || (allowMajorant && isMajorant); }
void unsetMajorant() {
assert(isMajorant);
isMajorant = false;
}
protected: protected:
EdittableChunk(); EdittableChunk();
/// Is temporary average /// Animated changes
const bool isAverage = false; /// MAYBE: sort by delay
/// Is temporary full valued
bool isMajorant = false;
/// Temporary changes
std::vector<Chunk::Edit> edits; std::vector<Chunk::Edit> edits;
/// Require update /// Require update
bool upToDate = true; bool upToDate = true;

View File

@ -68,6 +68,21 @@ namespace world {
constexpr inline bool is_full() const { constexpr inline bool is_full() const {
return density() == DENSITY_MAX && !materials::transparency[material()]; return density() == DENSITY_MAX && !materials::transparency[material()];
} }
/// Value after fill operation
inline Voxel const filled(Voxel in, float ratio) {
if (ratio >= 1)
return in;
const world::Voxel::density_t dst = in.density() * ratio;
if (!in.is_material())
return world::Voxel(material(), world::Voxel::DENSITY_MAX-dst, swap());
if (is_material() && density() > dst)
return Voxel(value);
return world::Voxel(in.material(), dst, in.swap());
}
}; };
/// Stock of material /// Stock of material
struct Item { struct Item {

View File

@ -16,6 +16,8 @@ namespace net::server {
picoquic_set_callback(cnx, connection_callback, peer); picoquic_set_callback(cnx, connection_callback, peer);
} }
assert(peer == nullptr || peer->contains(cnx) || fin_or_event == picoquic_callback_close); assert(peer == nullptr || peer->contains(cnx) || fin_or_event == picoquic_callback_close);
} else if (peer == (void*)server) {
peer = nullptr;
} }
assert(v_stream_ctx == nullptr || ((net::stream_ctx*)v_stream_ctx)->stream_id == stream_id); assert(v_stream_ctx == nullptr || ((net::stream_ctx*)v_stream_ctx)->stream_id == stream_id);

View File

@ -10,6 +10,7 @@ enum queue: uint8_t {
GLOBAL = 0, GLOBAL = 0,
ENTITY, ENTITY,
CHUNK, CHUNK,
EDIT,
count count
}; };

View File

@ -13,8 +13,9 @@
using namespace world::server; using namespace world::server;
const auto AREAS_FILE = "/areas.idx"; constexpr auto AREAS_FILE = "/areas.idx";
const auto COMPRESSION_PREFIX = (uint8_t)net::server_packet_type::COMPRESSION; constexpr auto COMPRESSION_PREFIX = (uint8_t)net::server_packet_type::COMPRESSION;
constexpr auto PREDICTABLE = true;
Universe::Universe(const Universe::options &options): host(options.connection, Universe::Universe(const Universe::options &options): host(options.connection,
[&](net::server::Peer* peer) { return onConnect(peer); }, [&](net::server::Peer* peer) { return onConnect(peer); },
@ -164,6 +165,10 @@ struct net_client {
} }
pendingChunks.insert(it, std::make_pair(in, dist)); pendingChunks.insert(it, std::make_pair(in, dist));
} }
bool handleEdits = false;
// MAYBE: client size ordering
std::queue<world::action::FillShape> pendingEdits;
}; };
void Universe::saveAll(bool remove) { void Universe::saveAll(bool remove) {
@ -223,23 +228,30 @@ void Universe::pull() {
host.pull(); host.pull();
host.iterPeers([&](net::server::Peer *peer) { host.iterPeers([&](net::server::Peer *peer) {
auto data = peer->getCtx<net_client>(); auto data = peer->getCtx<net_client>();
if (data == nullptr || data->pendingChunks.empty()) if (data == nullptr)
return; return;
//TODO: use congestion if (data->pendingEdits.empty() && peer->queueSize(net::server::queue::EDIT) == 0) {
area_<chunk_pos> pending; peer->send(net::PacketWriter::Of(net::server_packet_type::EDITS, data->pendingEdits.front()));
size_t i = peer->queueSize(net::server::queue::CHUNK); data->pendingEdits.pop();
while(i <= 4 && data->popPendingChunk(pending)) {
if (const auto it = areas.find(pending.first); it != areas.end()) {
if (const auto chunk = it->second->getChunks().findInRange(pending.second)) {
peer->send(serializeChunk(pending, std::dynamic_pointer_cast<Chunk>(chunk.value())), net::server::queue::CHUNK);
i++;
}
}
} }
if (data->pendingChunks.empty()) if (!data->pendingChunks.empty()) {
peer->send(net::PacketWriter(net::server_packet_type::CHUNK, 0).finish()); //FIXME: must be received after last chunk //TODO: use congestion
area_<chunk_pos> pending;
size_t i = peer->queueSize(net::server::queue::CHUNK);
while(i <= 4 && data->popPendingChunk(pending)) {
if (const auto it = areas.find(pending.first); it != areas.end()) {
if (const auto chunk = it->second->getChunks().findInRange(pending.second)) {
peer->send(serializeChunk(pending, std::dynamic_pointer_cast<Chunk>(chunk.value())), net::server::queue::CHUNK);
i++;
}
}
}
if (data->pendingChunks.empty())
peer->send(net::PacketWriter(net::server_packet_type::CHUNK, 0).finish()); //FIXME: must be received after last chunk
}
}); });
} }
@ -451,17 +463,23 @@ void Universe::update(float deltaTime) {
std::optional<uint16_t> Universe::onConnect(net::server::Peer* peer) { std::optional<uint16_t> Universe::onConnect(net::server::Peer* peer) {
ZoneScopedN("Connect"); ZoneScopedN("Connect");
using namespace net;
LOG_I("Client connect from " << peer->getAddress()); LOG_I("Client connect from " << peer->getAddress());
net_client* client = new net_client(entities.at(PLAYER_ENTITY_ID).instances.emplace(Entity::Instance{spawnPoint, glm::vec3(0)})); net_client* client = new net_client(entities.at(PLAYER_ENTITY_ID).instances.emplace(Entity::Instance{spawnPoint, glm::vec3(0)}));
peer->ctx = client; peer->ctx = client;
peer->send(net::PacketWriter::Of(net::server_packet_type::CAPABILITIES, loadDistance)); {
auto packet = PacketWriter(server_packet_type::CAPABILITIES, sizeof(loadDistance) + sizeof(bool));
packet.write(loadDistance);
packet.write(PREDICTABLE);
peer->send(packet.finish());
}
//TODO: lock while not received //TODO: lock while not received
peer->send(data::out_buffer(data::out_view((uint8_t*)dict_content.data(), dict_content.size()), nullptr), net::server::queue::CHUNK); peer->send(data::out_buffer(data::out_view((uint8_t*)dict_content.data(), dict_content.size()), nullptr), net::server::queue::CHUNK);
{ {
auto player = findEntity(PLAYER_ENTITY_ID, client->instanceId); auto player = findEntity(PLAYER_ENTITY_ID, client->instanceId);
auto packet = net::PacketWriter(net::server_packet_type::TELEPORT, sizeof(size_t) + sizeof(voxel_pos)); auto packet = PacketWriter(server_packet_type::TELEPORT, sizeof(size_t) + sizeof(voxel_pos));
packet.write(client->instanceId.index); packet.write(client->instanceId.index);
packet.write(player->pos.as_voxel()); packet.write(player->pos.as_voxel());
peer->send(packet.finish()); peer->send(packet.finish());
@ -469,7 +487,7 @@ std::optional<uint16_t> Universe::onConnect(net::server::Peer* peer) {
} }
{ {
constexpr auto ITEM_SIZE = sizeof(entity_id::index) + sizeof(glm::vec3) * 2; constexpr auto ITEM_SIZE = sizeof(entity_id::index) + sizeof(glm::vec3) * 2;
auto packet = net::PacketWriter(net::server_packet_type::ENTITY_TYPES, ITEM_SIZE * entities.size()); auto packet = PacketWriter(server_packet_type::ENTITY_TYPES, ITEM_SIZE * entities.size());
entities.iter([&](entity_id id, const Entity &entity) { entities.iter([&](entity_id id, const Entity &entity) {
packet.write(id.index); packet.write(id.index);
packet.write(entity.size); packet.write(entity.size);
@ -507,6 +525,14 @@ bool Universe::onPacket(net::server::Peer *peer, const data::out_view &buf, net:
} }
switch (type) { switch (type) {
case client_packet_type::CAPABILITIES: {
auto data = peer->getCtx<net_client>();
packet.read(data->handleEdits);
if (!PREDICTABLE && data->handleEdits) {
LOG_E("Client misread capabilities");
}
break;
}
case client_packet_type::MOVE: { case client_packet_type::MOVE: {
auto data = peer->getCtx<net_client>(); auto data = peer->getCtx<net_client>();
if (voxel_pos pos; !packet.read(pos) || if (voxel_pos pos; !packet.read(pos) ||
@ -522,6 +548,12 @@ bool Universe::onPacket(net::server::Peer *peer, const data::out_view &buf, net:
LOG_T("Entity in solid fill area"); LOG_T("Entity in solid fill area");
break; break;
} }
host.iterPeers([&](net::server::Peer *peer) {
auto data = peer->getCtx<net_client>();
//MAYBE: only in range
if (data && data->handleEdits)
data->pendingEdits.push(*fill);
});
set(fill->pos, fill->radius, fill->shape, fill->val); set(fill->pos, fill->radius, fill->shape, fill->val);
//TODO: handle inventory //TODO: handle inventory
} else { } else {
@ -672,19 +704,7 @@ world::ItemList Universe::set(const area_<voxel_pos>& pos, int radius, action::S
if(const auto chunk = it->second->setChunks().findInRange(split.first)) { if(const auto chunk = it->second->setChunks().findInRange(split.first)) {
auto ck = std::dynamic_pointer_cast<Chunk>(chunk.value()); auto ck = std::dynamic_pointer_cast<Chunk>(chunk.value());
auto prev = ck->get(split.second); auto prev = ck->get(split.second);
const auto next = [&] { const auto next = prev.filled(val, point.second);
if (point.second == 1)
return val;
const world::Voxel::density_t density = val.density() * point.second;
if (!val.is_material())
return world::Voxel(prev.material(), world::Voxel::DENSITY_MAX-density);
if (prev.is_material() && prev.density() > density)
return prev;
return world::Voxel(val.material(), density);
}();
if(prev.value != next.value) { if(prev.value != next.value) {
//TODO: apply break table //TODO: apply break table
//TODO: inventory //TODO: inventory
@ -696,22 +716,35 @@ world::ItemList Universe::set(const area_<voxel_pos>& pos, int radius, action::S
} }
} }
ZoneScopedN("Packet"); bool stupidClient = false;
size_t size = sizeof(area_id); host.iterPeers([&](net::server::Peer *peer) {
for(const auto& part: edits) { auto data = peer->getCtx<net_client>();
size += sizeof(chunk_pos); if (data && !data->handleEdits)
size += sizeof(chunk_voxel_idx); stupidClient = true;
size += sizeof(Chunk::Edit) * part.second.size(); });
if (stupidClient) {
ZoneScopedN("Packet");
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::PacketWriter(net::server_packet_type::EDITS, size);
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));
}
auto buffer = packet.finish();
host.iterPeers([&](net::server::Peer *peer) {
//MAYBE: only in range
auto data = peer->getCtx<net_client>();
if (data && !data->handleEdits)
peer->send(buffer, net::server::queue::CHUNK);
});
} }
auto packet = net::PacketWriter(net::server_packet_type::EDITS, size);
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));
}
//MAYBE: only in range
host.sendBroadcast(packet.finish(), net::server::queue::CHUNK);
} }
return list; return list;
} }