diff --git a/src/client/contouring/FlatDualMC.cpp b/src/client/contouring/FlatDualMC.cpp index 2158b21..b519e15 100644 --- a/src/client/contouring/FlatDualMC.cpp +++ b/src/client/contouring/FlatDualMC.cpp @@ -370,10 +370,9 @@ namespace contouring { const auto area_offset = glm::divide(std::get<0>(area.first).as_voxel()); robin_hood::unordered_set done; for (const auto& occ: occlusion) { - const geometry::Ray ray(start, occ, dist); - std::vector points; //TODO: iterator - ray.grid(points); - for(auto& point: points) { + geometry::Ray::iterator it(geometry::Ray(start, occ, dist)); + glm::llvec3 point; + while (it.next(point)) { auto it = area.second.find(glm::lvec3(point) - area_offset); const auto buffer = solid ? it->second.first : it->second.second; if(it != area.second.end() && buffer != NULL && done.insert(it->first).second) { diff --git a/src/client/net/Client.cpp b/src/client/net/Client.cpp index 14b9848..6305c30 100644 --- a/src/client/net/Client.cpp +++ b/src/client/net/Client.cpp @@ -61,9 +61,17 @@ int Client::connectionCallback(uint64_t stream_id, uint8_t* bytes, size_t length case picoquic_callback_stream_fin: { assert(stream_ctx::IsServerId(stream_id)); auto stream_ctx = (in_stream_ctx *)v_stream_ctx; + const auto is_fin = fin_or_event == picoquic_callback_stream_fin; - /* Data arrival on stream #x, maybe with fin mark */ - if (stream_ctx == NULL) { // New stream from server + if (stream_ctx == NULL) { + if (is_fin) { // Single frame packet + if (length > 0) { + onPacket(data::out_view(bytes, length), PacketFlags::TINY); + } + reset(stream_id); + break; + } + // New long stream from server stream_ctx = receive(stream_id); } @@ -71,7 +79,7 @@ int Client::connectionCallback(uint64_t stream_id, uint8_t* bytes, size_t length stream_ctx->buffer.write(bytes, length); } - if (fin_or_event == picoquic_callback_stream_fin) { + if (is_fin) { if (onPacket(data::out_view(stream_ctx->buffer.data.data(), stream_ctx->buffer.data.size()), PacketFlags::NONE)) { close(stream_ctx); } else { diff --git a/src/core/data/file.hpp b/src/core/data/file.hpp index 55992cb..b8d85fa 100644 --- a/src/core/data/file.hpp +++ b/src/core/data/file.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include "../data/mem.hpp" #include "../utils/logger.hpp" namespace data { @@ -9,7 +9,9 @@ namespace data { class file_content: public std::vector { public: // Read first - file_content(const std::vector& paths): std::vector() { + file_content(const std::vector& paths, data::out_view prefix = data::out_view(nullptr, 0)): + std::vector(), prefix_size(prefix.size()) + { std::ifstream is = [&]() { for(auto& path: paths) { std::ifstream is(path, std::ios::in | std::ios::binary | std::ios::ate); @@ -22,10 +24,18 @@ public: }(); const auto end = is.tellg(); is.seekg(0, std::ios::beg); - resize(end - is.tellg()); - is.read(data(), size()); + resize(end - is.tellg() + prefix_size); + memcpy(data(), prefix.ptr, prefix_size); + is.read(data() + prefix_size, size()); is.close(); } + + size_t prefix_size; + + /// Data after prefix + data::out_view content() const { + return data::out_view((const uint8_t*)data() + prefix_size, size() - prefix_size); + } }; } \ No newline at end of file diff --git a/src/core/data/mem.hpp b/src/core/data/mem.hpp index a542f65..99b597b 100644 --- a/src/core/data/mem.hpp +++ b/src/core/data/mem.hpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace data { diff --git a/src/core/geometry/Ray.hpp b/src/core/geometry/Ray.hpp index 62e02d1..40cd29e 100644 --- a/src/core/geometry/Ray.hpp +++ b/src/core/geometry/Ray.hpp @@ -83,6 +83,55 @@ namespace geometry { } points.push_back(current); } + /// Iterator implementation of grid + struct iterator { + enum class axis { X, Y, Z }; + + iterator(const Ray& r): current(r.from.as_voxel()), + d(r.dir * r.dist), l(glm::abs(d)), + dir((l.x >= l.y) && (l.x >= l.z) ? axis::X : ((l.y >= l.x) && (l.y >= l.z) ? axis::Y : axis::Z)), + count(dir == axis::X ? l.x : (dir == axis::Y ? l.y : l.z)), + inc(swap(glm::llvec3((d.x < 0) ? -1 : 1, (d.y < 0) ? -1 : 1, (d.z < 0) ? -1 : 1), dir)), + delta(swap(l << 1ll, dir)), err_1(delta.y - count), err_2(delta.z - count) { } + + static constexpr inline glm::llvec3 swap(const glm::llvec3& in, const axis dir) { return dir == axis::X ? in : + (dir == axis::Y ? glm::llvec3(in.y, in.z, in.x) : glm::llvec3(in.z, in.x, in.y)); } + + static constexpr inline long long& get(glm::llvec3& in, const axis dir, const axis target) { + const axis v = (axis)(((int)dir + (int)target) % 3); + return v == axis::X ? in.x : (v == axis::Y ? in.y : in.z); + } + + bool next(glm::llvec3& out) { + if (count < 0) + return false; + + count--; + out = current; + if (err_1 > 0) { + get(current, dir, axis::Y) += inc.y; + err_1 -= delta.x; + } + if (err_2 > 0) { + get(current, dir, axis::Z) += inc.z; + err_2 -= delta.x; + } + err_1 += delta.y; + err_2 += delta.z; + get(current, dir, axis::X) += inc.x; + return true; + } + + glm::llvec3 current; + const glm::llvec3 d; + const glm::llvec3 l; + const axis dir; + glm::llvec3::value_type count; + const glm::llvec3 inc; + const glm::llvec3 delta; + int err_1; + int err_2; + }; IBox::ContainmentType intersect(const IBox& box) const { const glm::llvec3 start = from.as_voxel(); diff --git a/src/core/net/Context.hpp b/src/core/net/Context.hpp index deecf2c..717dc4e 100644 --- a/src/core/net/Context.hpp +++ b/src/core/net/Context.hpp @@ -31,6 +31,7 @@ struct in_stream_ctx: stream_ctx { enum class PacketFlags { NONE = 0, DATAGRAM = 1 << 0, + TINY = 1 << 1, }; /// Abstract QUIC context diff --git a/src/core/net/io.hpp b/src/core/net/io.hpp index dc82b65..c1e19f8 100644 --- a/src/core/net/io.hpp +++ b/src/core/net/io.hpp @@ -6,6 +6,8 @@ namespace net { //TODO: preallocate static data pool + +/// Write allocated packets class PacketWriter final { public: PacketWriter(size_t size): buffer((uint8_t*)malloc(size), size) { } @@ -27,6 +29,37 @@ public: write(&d, sizeof(d)); } + struct varying_part { + varying_part(data::in_view &buffer): buffer(buffer), visible_size(0) { } + ~varying_part() { + buffer.siz = buffer.cur + visible_size; + buffer.cur = buffer.siz; + } + + data::in_view &buffer; + size_t visible_size; + + constexpr size_t size() const { return visible_size; } + void* data() { return buffer.writeTo(0); } + void reserve(size_t target) { + if (target >= buffer.siz - buffer.cur) { + auto old = buffer.ptr; + buffer.ptr = (uint8_t*)malloc(target); + memcpy(buffer.ptr, old, buffer.siz); + free(old); + buffer.siz = target; + } + } + void resize(size_t target) { + reserve(target); + visible_size = target; + } + }; + /// Only from resize, write, resize down + varying_part varying() { + return varying_part(buffer); + } + bool isFull() const { return buffer.isDone(); } diff --git a/src/core/utils/zctx.hpp b/src/core/utils/zctx.hpp index a7b6f0c..cec8c27 100644 --- a/src/core/utils/zctx.hpp +++ b/src/core/utils/zctx.hpp @@ -1,8 +1,7 @@ #pragma once #include -#include -#include +#include "../data/mem.hpp" #include "logger.hpp" namespace zstd { @@ -73,6 +72,7 @@ namespace zstd { class dict_set { public: dict_set(const std::vector& data) { load(data.data(), data.size()); } + dict_set(const data::out_view& data) { load(data.data(), data.size()); } ~dict_set() { ZSTD_freeCDict(c); ZSTD_freeDDict(d); diff --git a/src/core/world/raycast.hpp b/src/core/world/raycast.hpp index cf5f025..b40efdd 100644 --- a/src/core/world/raycast.hpp +++ b/src/core/world/raycast.hpp @@ -7,20 +7,19 @@ namespace world { template Universe::ray_result Raycast(const geometry::Ray& ray, const Areas& areas) { - //TODO: iterator //MAYBE: ray + offset to get float precision - std::vector points; - ray.grid(points); Universe::ray_result target; - size_t dist = points.size(); + size_t dist = UINT32_MAX - 1; 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 chunk = nullptr; chunk_pos chunk_vec(INT_MAX); - for (size_t i = 0; i < dist; i++) { - const auto pos = points[i] - offset; + geometry::Ray::iterator it(ray); + glm::llvec3 point; + for (size_t i = 0; i < dist && it.next(point); i++) { + const auto pos = point - offset; const chunk_pos cPos = glm::divide(pos); if(cPos != chunk_vec) { if (const auto it = chunks.find(cPos); it != chunks.end()) { diff --git a/src/server/net/Server.cpp b/src/server/net/Server.cpp index 5cb596f..7278600 100644 --- a/src/server/net/Server.cpp +++ b/src/server/net/Server.cpp @@ -58,9 +58,17 @@ int Server::connectionCallback(Peer* peer, uint64_t stream_id, uint8_t* bytes, s case picoquic_callback_stream_fin: { assert(stream_ctx::IsClientId(stream_id)); auto stream_ctx = (in_stream_ctx *)v_stream_ctx; + const auto is_fin = fin_or_event == picoquic_callback_stream_fin; - /* Data arrival on stream #x, maybe with fin mark */ - if (stream_ctx == NULL) { // New stream from peer + if (stream_ctx == NULL) { + if (is_fin) { // Single frame packet + if (length > 0) { + onPacket(peer, data::out_view(bytes, length), PacketFlags::TINY); + } + peer->reset(stream_id); + break; + } + // New long stream from server stream_ctx = peer->receive(stream_id); } @@ -68,7 +76,7 @@ int Server::connectionCallback(Peer* peer, uint64_t stream_id, uint8_t* bytes, s stream_ctx->buffer.write(bytes, length); } - if (fin_or_event == picoquic_callback_stream_fin) { + if (is_fin) { if (onPacket(peer, data::out_view(stream_ctx->buffer.data.data(), stream_ctx->buffer.data.size()), PacketFlags::NONE)) { peer->close(stream_ctx); } else { diff --git a/src/server/world/Universe.cpp b/src/server/world/Universe.cpp index d93412c..f324ddb 100644 --- a/src/server/world/Universe.cpp +++ b/src/server/world/Universe.cpp @@ -14,12 +14,14 @@ using namespace world::server; const auto AREAS_FILE = "/areas.idx"; +const auto COMPRESSION_PREFIX = (uint8_t)net::server_packet_type::COMPRESSION; Universe::Universe(const Universe::options &options): host(options.connection, [&](net::server::Peer* peer) { return onConnect(peer); }, [&](net::server::Peer* peer, bool is_app, uint16_t reason) { return onDisconnect(peer, is_app, reason); }, [&](net::server::Peer* peer, const data::out_view &buf, net::PacketFlags flags) { return onPacket(peer, buf, flags); } - ), dict_content({options.folderPath + "/zstd.dict", "content/zstd.dict"}), dicts(dict_content), dict_write_ctx(dicts.make_writer()) + ), dict_content({options.folderPath + "/zstd.dict", "content/zstd.dict"}, data::out_view(&COMPRESSION_PREFIX, sizeof(COMPRESSION_PREFIX))), + dicts(dict_content.content()), dict_write_ctx(dicts.make_writer()) { setOptions(options); folderPath = options.folderPath; @@ -453,9 +455,7 @@ std::optional Universe::onConnect(net::server::Peer* peer) { peer->send(net::PacketWriter::Of(net::server_packet_type::CAPABILITIES, loadDistance)); //TODO: lock while not received - //MAYBE: add net::server_packet_type::COMPRESSION to dict_content: zero copy - peer->send(net::PacketWriter::Of(net::server_packet_type::COMPRESSION, dict_content.data(), dict_content.size()), 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 packet = net::PacketWriter(net::server_packet_type::TELEPORT, sizeof(size_t) + sizeof(voxel_pos)); @@ -582,12 +582,12 @@ data::out_buffer Universe::serializeChunk(area_ id, const std::shared ZoneScopedN("Chunk"); std::ostringstream out; data->write(out); - std::vector buffer; - //FIXME: avoid buffer copy - dict_write_ctx.compress(out.str(), buffer); - auto packet = net::PacketWriter(net::server_packet_type::CHUNK, sizeof(id) + buffer.size()); + auto packet = net::PacketWriter(net::server_packet_type::CHUNK, sizeof(id)); packet.write(id); - packet.write(buffer.data(), buffer.size()); + { + auto vec = packet.varying(); + dict_write_ctx.compress(out.str(), vec); + } return packet.finish(); } void Universe::broadcastMessage(const std::string& text) {