Client size edits
parent
2bb02bef1f
commit
fa482b37f3
16
TODO.md
16
TODO.md
|
@ -20,30 +20,30 @@
|
|||
## Hello world
|
||||
|
||||
- [~] Map stream
|
||||
- [ ] Modifications compression
|
||||
- [ ] Local prediction
|
||||
- [ ] Contouring service
|
||||
- [~] Edit
|
||||
- [ ] Compression (raw)
|
||||
- [x] Shape iterators
|
||||
- [ ] Anchor
|
||||
- [x] Prevent suffocation
|
||||
- [ ] Local prediction
|
||||
- [x] Local prediction
|
||||
- [~] Occlusion Culling
|
||||
- [ ] Iterator ray
|
||||
- [~] Iterator ray
|
||||
- [ ] Cast from chunk center
|
||||
- [x] Transparency
|
||||
- [~] Entities
|
||||
- [ ] Collide
|
||||
- [ ] Get models
|
||||
- [ ] Reduce compile unit count
|
||||
- [ ] Review documentation
|
||||
- [~] Review documentation
|
||||
- [ ] Clean kick
|
||||
|
||||
## Hello universe
|
||||
|
||||
- [ ] CI build
|
||||
- CMake package
|
||||
- GitLab / Drone pipeline
|
||||
- [~] CI build
|
||||
- [ ] CMake package
|
||||
- [x] GitLab CI
|
||||
- [ ] Universe
|
||||
- [ ] Galaxy
|
||||
- [ ] Rotation
|
||||
|
@ -79,7 +79,7 @@
|
|||
- [ ] Slash screen
|
||||
- [ ] Start/Pause menu
|
||||
- [x] QUIC protocol
|
||||
- [ ] 8 for 1 contouring problem
|
||||
- [~] 8 for 1 contouring problem
|
||||
- [ ] Use in memory protocol (to replace server_handle)
|
||||
- [ ] Octree
|
||||
- [ ] Better Lod
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
|
||||
world.useAverages = config["world"]["use_averages"].value_or(world.useAverages);
|
||||
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);
|
||||
|
||||
#ifndef STANDALONE
|
||||
|
@ -144,6 +146,8 @@ public:
|
|||
{"keep_distance", world.keepDistance},
|
||||
{"use_averages", world.useAverages},
|
||||
{"trust_majorant", world.trustMajorant},
|
||||
{"edit_prediction", world.editPrediction},
|
||||
{"edit_handling", world.editHandling},
|
||||
{"voxel_density", voxel_density}
|
||||
}));
|
||||
if(connection.has_value()) {
|
||||
|
|
|
@ -4,12 +4,48 @@
|
|||
|
||||
namespace world::client {
|
||||
|
||||
class Chunk final: public EdittableChunk {
|
||||
public:
|
||||
Chunk(): world::Chunk(), EdittableChunk() { }
|
||||
};
|
||||
class Chunk final: public EdittableChunk {
|
||||
public:
|
||||
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
|
||||
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>();
|
||||
std::optional<Faces> update(float deltaTime, bool animate) override {
|
||||
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>();
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
#include "Area.hpp"
|
||||
#include "../contouring/Abstract.hpp"
|
||||
#include "../../core/world/raycast.hpp"
|
||||
#include "../../core/world/iterators.hpp"
|
||||
#include "../../core/net/io.hpp"
|
||||
#include "../../core/utils/logger.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)) {
|
||||
it_c = chunks.erase(it_c);
|
||||
} 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());
|
||||
}
|
||||
++it_c;
|
||||
|
@ -63,7 +64,7 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
|
|||
const auto p = diff + chunk_pos(x, y, z);
|
||||
if (dist2 <= queryDistance * queryDistance && chunks.inRange(p)) {
|
||||
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
|
||||
{
|
||||
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);
|
||||
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)) {
|
||||
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);
|
||||
chunks.emplace(p, std::dynamic_pointer_cast<world::Chunk>(ck));
|
||||
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(missingChunks.data(), missingChunks.size() * sizeof(chunk_pos));
|
||||
peer.send(packet.finish());
|
||||
LOG("Query " << missingChunks.size());
|
||||
}
|
||||
if(!missingRegions.empty()) {
|
||||
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;
|
||||
}
|
||||
|
||||
case server_packet_type::CAPABILITIES:
|
||||
return packet.read(serverDistance);
|
||||
case server_packet_type::CAPABILITIES: {
|
||||
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: {
|
||||
const auto remain = packet.readAll();
|
||||
|
@ -222,7 +233,6 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
|
|||
if (voxel.swap())
|
||||
full++;
|
||||
}
|
||||
LOG(full << "/" << total);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -257,7 +267,7 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
|
|||
}
|
||||
data::vec_istream idata(buffer);
|
||||
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);
|
||||
auto ptr = std::dynamic_pointer_cast<world::Chunk>(ck);
|
||||
auto &chunks = it->second->setChunks();
|
||||
|
@ -271,6 +281,38 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
|
|||
|
||||
case server_packet_type::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;
|
||||
if(!packet.read(id))
|
||||
break;
|
||||
|
@ -287,7 +329,7 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
|
|||
chunk_voxel_idx count = 0;
|
||||
packet.read(count);
|
||||
if (auto ptr = it->second->setChunks().findInRange(pos)) {
|
||||
auto chunk = std::dynamic_pointer_cast<world::client::EdittableChunk>(ptr.value());
|
||||
auto chunk = std::dynamic_pointer_cast<Chunk>(ptr.value());
|
||||
for (auto i = 0; i < count && !packet.isFull(); i++) {
|
||||
Chunk::Edit 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()));
|
||||
} else if(const auto fill = std::get_if<action::FillShape>(&action)) {
|
||||
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 {
|
||||
LOG_W("Bad action " << action.index());
|
||||
}
|
||||
|
|
|
@ -25,6 +25,10 @@ namespace world::client {
|
|||
/// Dont query chunks with absolute majorant average
|
||||
/// Avoid querying whole space but may cause missed edits in those chunks
|
||||
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 {
|
||||
connection(): net::address{"localhost", 4242} { }
|
||||
|
|
|
@ -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) {
|
||||
return cnx != nullptr ? (is_app ? cnx->remote_application_error : cnx->remote_error) : 42;
|
||||
}
|
||||
|
|
|
@ -103,7 +103,10 @@ public:
|
|||
|
||||
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);
|
||||
/// Ip address and port
|
||||
std::string getAddress();
|
||||
|
||||
/// Send reliable data
|
||||
|
|
|
@ -45,30 +45,33 @@ enum class server_packet_type: uint8_t {
|
|||
/// {area_<chunk_pos>, zstd<chunk rle>}
|
||||
/// empty: all sent
|
||||
CHUNK = 18,
|
||||
/// Chunk changes
|
||||
/// {area_id, {chunk_pos, ushort(count), Chunk::Edit[]}[]} notify
|
||||
/// MAYBE: compress
|
||||
EDITS = 19,
|
||||
/// Uncompressed chunk changes
|
||||
/// {area_id, {chunk_pos, ushort(count), Chunk::Edit[]}[]}
|
||||
RAW_EDITS = 19,
|
||||
/// Chunk changes instruction
|
||||
/// action::FillShape
|
||||
EDITS = 20,
|
||||
|
||||
/// Declare entities types
|
||||
/// {size_t(index), vec3(size), vec3(scale)}
|
||||
/// TODO: zstd<chunk rle>
|
||||
ENTITY_TYPES = 20,
|
||||
ENTITY_TYPES = 32,
|
||||
|
||||
/// Update entities instances position and velocity
|
||||
/// {size_t(entity), size_t(count), {size_t(index), ifvec3(pos), vec3(velocity)}[]}[]
|
||||
ENTITIES = 21,
|
||||
ENTITIES = 33,
|
||||
|
||||
/// World compression dictionary
|
||||
/// zstd dict
|
||||
COMPRESSION = 24,
|
||||
COMPRESSION = 64,
|
||||
/// Server capabilities
|
||||
/// ushort(loadDistance), MAYBE: more
|
||||
CAPABILITIES = 25,
|
||||
/// ushort(loadDistance), bool(predictable)
|
||||
//MAYBE: use uint8_t flags
|
||||
CAPABILITIES = 65,
|
||||
|
||||
/// Public chat message
|
||||
/// char[] (not null terminated)
|
||||
MESSAGE = 29,
|
||||
MESSAGE = 129,
|
||||
};
|
||||
/// Packets from client to server
|
||||
enum class client_packet_type: uint8_t {
|
||||
|
@ -91,6 +94,11 @@ enum class client_packet_type: uint8_t {
|
|||
/// Position update (unreliable)
|
||||
/// (sequence)uint64_t voxel_pos
|
||||
MOVE = 16,
|
||||
|
||||
/// Client capabilities
|
||||
/// bool(edit_handling)
|
||||
//MAYBE: use uint8_t flags
|
||||
CAPABILITIES = 65,
|
||||
};
|
||||
|
||||
constexpr auto MAX_PENDING_CHUNK_COUNT = 256;
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
using namespace world::client;
|
||||
|
||||
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() { }
|
||||
|
||||
std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) {
|
||||
|
|
|
@ -7,9 +7,6 @@ using namespace geometry;
|
|||
namespace world::client {
|
||||
class EdittableChunk: public virtual world::Chunk {
|
||||
public:
|
||||
EdittableChunk(std::istream &is);
|
||||
/// Create from average
|
||||
EdittableChunk(Voxel val);
|
||||
virtual ~EdittableChunk();
|
||||
|
||||
/// Update voxels
|
||||
|
@ -30,21 +27,11 @@ namespace world::client {
|
|||
|
||||
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:
|
||||
EdittableChunk();
|
||||
|
||||
/// Is temporary average
|
||||
const bool isAverage = false;
|
||||
/// Is temporary full valued
|
||||
bool isMajorant = false;
|
||||
|
||||
/// Temporary changes
|
||||
/// Animated changes
|
||||
/// MAYBE: sort by delay
|
||||
std::vector<Chunk::Edit> edits;
|
||||
/// Require update
|
||||
bool upToDate = true;
|
||||
|
|
|
@ -68,6 +68,21 @@ namespace world {
|
|||
constexpr inline bool is_full() const {
|
||||
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
|
||||
struct Item {
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace net::server {
|
|||
picoquic_set_callback(cnx, connection_callback, peer);
|
||||
}
|
||||
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);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ enum queue: uint8_t {
|
|||
GLOBAL = 0,
|
||||
ENTITY,
|
||||
CHUNK,
|
||||
EDIT,
|
||||
count
|
||||
};
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
|
||||
using namespace world::server;
|
||||
|
||||
const auto AREAS_FILE = "/areas.idx";
|
||||
const auto COMPRESSION_PREFIX = (uint8_t)net::server_packet_type::COMPRESSION;
|
||||
constexpr auto AREAS_FILE = "/areas.idx";
|
||||
constexpr auto COMPRESSION_PREFIX = (uint8_t)net::server_packet_type::COMPRESSION;
|
||||
constexpr auto PREDICTABLE = true;
|
||||
|
||||
Universe::Universe(const Universe::options &options): host(options.connection,
|
||||
[&](net::server::Peer* peer) { return onConnect(peer); },
|
||||
|
@ -164,6 +165,10 @@ struct net_client {
|
|||
}
|
||||
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) {
|
||||
|
@ -223,23 +228,30 @@ void Universe::pull() {
|
|||
host.pull();
|
||||
host.iterPeers([&](net::server::Peer *peer) {
|
||||
auto data = peer->getCtx<net_client>();
|
||||
if (data == nullptr || data->pendingChunks.empty())
|
||||
if (data == nullptr)
|
||||
return;
|
||||
|
||||
//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->pendingEdits.empty() && peer->queueSize(net::server::queue::EDIT) == 0) {
|
||||
peer->send(net::PacketWriter::Of(net::server_packet_type::EDITS, data->pendingEdits.front()));
|
||||
data->pendingEdits.pop();
|
||||
}
|
||||
|
||||
if (data->pendingChunks.empty())
|
||||
peer->send(net::PacketWriter(net::server_packet_type::CHUNK, 0).finish()); //FIXME: must be received after last chunk
|
||||
if (!data->pendingChunks.empty()) {
|
||||
//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) {
|
||||
ZoneScopedN("Connect");
|
||||
using namespace net;
|
||||
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)}));
|
||||
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
|
||||
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 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(player->pos.as_voxel());
|
||||
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;
|
||||
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) {
|
||||
packet.write(id.index);
|
||||
packet.write(entity.size);
|
||||
|
@ -507,6 +525,14 @@ bool Universe::onPacket(net::server::Peer *peer, const data::out_view &buf, net:
|
|||
}
|
||||
|
||||
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: {
|
||||
auto data = peer->getCtx<net_client>();
|
||||
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");
|
||||
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);
|
||||
//TODO: handle inventory
|
||||
} 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)) {
|
||||
auto ck = std::dynamic_pointer_cast<Chunk>(chunk.value());
|
||||
auto prev = ck->get(split.second);
|
||||
const auto next = [&] {
|
||||
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);
|
||||
}();
|
||||
const auto next = prev.filled(val, point.second);
|
||||
if(prev.value != next.value) {
|
||||
//TODO: apply break table
|
||||
//TODO: inventory
|
||||
|
@ -696,22 +716,35 @@ world::ItemList Universe::set(const area_<voxel_pos>& pos, int radius, action::S
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
bool stupidClient = false;
|
||||
host.iterPeers([&](net::server::Peer *peer) {
|
||||
auto data = peer->getCtx<net_client>();
|
||||
if (data && !data->handleEdits)
|
||||
stupidClient = true;
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue