diff --git a/TODO.md b/TODO.md index edd1ad4..c9b16fe 100644 --- a/TODO.md +++ b/TODO.md @@ -73,6 +73,10 @@ Released as `0.0.1`: `Pre alpha 1` - [ ] Inventory - [ ] Octree - [ ] Surface aware LOD + - [ ] All levels (0-5) + - [ ] Tree fill + - [ ] Big edit queue + - [ ] Update region level average - [ ] Area converter - [ ] Minecraft import - [ ] Base unit size change diff --git a/src/client/config.hpp b/src/client/config.hpp index 31d4332..0ef0a5a 100644 --- a/src/client/config.hpp +++ b/src/client/config.hpp @@ -240,7 +240,7 @@ public: float opacity = .8f; } console; - struct { + struct overlay_t { bool visible = true; int corner = 3; } overlay; diff --git a/src/client/contouring/Abstract.hpp b/src/client/contouring/Abstract.hpp index f5984f4..aa7a776 100644 --- a/src/client/contouring/Abstract.hpp +++ b/src/client/contouring/Abstract.hpp @@ -50,6 +50,9 @@ namespace contouring { /// Get pending elements virtual size_t getQueueSize() = 0; + /// Get max visible distance in chunks + virtual uint16_t getVisibleDist() const = 0; + struct area_info { world::transform absolute; world::cell_pos::value_type radius; diff --git a/src/client/contouring/FlatDualMC.cpp b/src/client/contouring/FlatDualMC.cpp index d3dfc5d..5062b58 100644 --- a/src/client/contouring/FlatDualMC.cpp +++ b/src/client/contouring/FlatDualMC.cpp @@ -311,24 +311,26 @@ namespace contouring { } void FlatDualMC::render(const surrounding::corners &surrounding, render::Model::Data::indices_t &out, std::vector &tmp, Layer layer) const { - const int SIZE = world::CHUNK_LENGTH + 3; + constexpr uint8_t SIZE = world::CHUNK_LENGTH + 3; std::array::Point, SIZE * SIZE * SIZE> grid; const auto &materials = world::module::Registry::Get()->getMaterials(); { ZoneScopedN("Load"); - const auto setCell = [&](int x, int y, int z, const world::Voxel &voxel) { - auto &cell = grid[((z * SIZE) + y) * SIZE + x]; + const auto setCell = [&](glm::idx i, const world::Voxel &voxel) { + auto &cell = grid[i]; cell.w = voxel.material(); cell.x = voxel.density_ratio() * (!materials.invisibilities[cell.w] && ((materials.transparencies[cell.w] && (layer && Layer::Transparent)) || (!materials.transparencies[cell.w] && (layer && Layer::Solid)))); }; - for (int z = 0; z < SIZE; z++) { - for (int y = 0; y < SIZE; y++) { - for (int x = 0; x < SIZE; x++) { + size_t i = 0; + for (uint8_t z = 0; z < SIZE; z++) { + for (uint8_t y = 0; y < SIZE; y++) { + for (uint8_t x = 0; x < SIZE; x++) { const auto &chunk = surrounding[(z >= world::CHUNK_LENGTH) + (y >= world::CHUNK_LENGTH)*2 + (x >= world::CHUNK_LENGTH)*4]; - const auto &voxel = chunk->get(glm::toIdx(x % world::CHUNK_LENGTH, y % world::CHUNK_LENGTH, z % world::CHUNK_LENGTH)); - setCell(x, y, z, voxel); + const auto &voxel = chunk->get(glm::toIdx(x & glm::IDX_MASK, y & glm::IDX_MASK, z & glm::IDX_MASK)); + setCell(i, voxel); + i++; }}} for (size_t i = 0; i < surrounding.size(); i++) { @@ -337,7 +339,7 @@ namespace contouring { for (auto it = edits.begin(); it != edits.end(); ++it) { auto p = offset + glm::ivec3(glm::fromIdx(it->first)); if(p.x < SIZE && p.y < SIZE && p.z < SIZE) { - setCell(p.x, p.y, p.z, it->second.value); + setCell(glm::toIdx(p, glm::ucvec3(SIZE)), it->second.value); } } } diff --git a/src/client/contouring/FlatDualMC.hpp b/src/client/contouring/FlatDualMC.hpp index ae54e03..76660e4 100644 --- a/src/client/contouring/FlatDualMC.hpp +++ b/src/client/contouring/FlatDualMC.hpp @@ -42,6 +42,8 @@ namespace contouring { render::Model* getModel(const world::model_id&) const override; void getInstancedModels(instanced_draw_call draw, const world::Elements&, const std::optional& frustum, const world::cell_pos& offset, world::node_id ignore) const override; + uint16_t getVisibleDist() const override { return keepDistance; } + private: struct area_models { std::unique_ptr main; diff --git a/src/client/contouring/dualmc.h b/src/client/contouring/dualmc.h index 7544b6e..20febb9 100644 --- a/src/client/contouring/dualmc.h +++ b/src/client/contouring/dualmc.h @@ -395,10 +395,7 @@ void DualMC::calculateDualPoint(int32_t const cx, int32_t const cy, int32_t c // compute the dual point as the mean of the face vertices belonging to the // original marching cubes face - Vertex p; - p.x = 0; - p.y = 0; - p.z = 0; + Vertex p(0, 0, 0, 0); int points = 0; T max = 0; diff --git a/src/client/render/UI.cpp b/src/client/render/UI.cpp index af77d57..649870d 100644 --- a/src/client/render/UI.cpp +++ b/src/client/render/UI.cpp @@ -4,6 +4,8 @@ #include #include "../state.hpp" #include "Window.hpp" +#include "core/utils/os.hpp" +#include "api/Buffers.hpp" #include "core/world/Elements.hpp" #include "version.h" #include @@ -115,38 +117,47 @@ void UI::Unload() { sInstance = nullptr; } -UI::Actions drawMenu(config::client::options &options, state::state &state, const std::vector& packs) { - const ImGuiIO &io = ImGui::GetIO(); - - if (options.overlay.visible) { - if (options.overlay.corner != -1) { - ImVec2 window_pos = ImVec2((options.overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (options.overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN); - ImVec2 window_pos_pivot = ImVec2((options.overlay.corner & 1) ? 1.0f : 0.0f, (options.overlay.corner & 2) ? 1.0f : 0.0f); +template +void drawOverlay(config::client::options::overlay_t& overlay, F inner) { + if (overlay.visible) { + const ImGuiIO &io = ImGui::GetIO(); + if (overlay.corner != -1) { + ImVec2 window_pos = ImVec2((overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN); + ImVec2 window_pos_pivot = ImVec2((overlay.corner & 1) ? 1.0f : 0.0f, (overlay.corner & 2) ? 1.0f : 0.0f); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); } ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background - ImGui::Begin("Overlay", &options.overlay.visible, (options.overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); + ImGui::Begin("Overlay", &overlay.visible, (overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + inner(); + ImGui::Text("RAM: %.3f/%.3f GB", os::GetProcess().memused / 1000000000.f, os::GetGlobal().ram.total / 1000000000.); + if (ImGui::BeginPopupContextWindow()) { - if (ImGui::MenuItem("Custom", NULL, options.overlay.corner == -1)) - options.overlay.corner = -1; - if (ImGui::MenuItem("Top-left", NULL, options.overlay.corner == 0)) - options.overlay.corner = 0; - if (ImGui::MenuItem("Top-right", NULL, options.overlay.corner == 1)) - options.overlay.corner = 1; - if (ImGui::MenuItem("Bottom-left", NULL, options.overlay.corner == 2)) - options.overlay.corner = 2; - if (ImGui::MenuItem("Bottom-right", NULL, options.overlay.corner == 3)) - options.overlay.corner = 3; - if (options.overlay.visible && ImGui::MenuItem("Close")) - options.overlay.visible = false; + if (ImGui::MenuItem("Custom", NULL, overlay.corner == -1)) + overlay.corner = -1; + if (ImGui::MenuItem("Top-left", NULL, overlay.corner == 0)) + overlay.corner = 0; + if (ImGui::MenuItem("Top-right", NULL, overlay.corner == 1)) + overlay.corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, overlay.corner == 2)) + overlay.corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, overlay.corner == 3)) + overlay.corner = 3; + if (overlay.visible && ImGui::MenuItem("Close")) + overlay.visible = false; ImGui::EndPopup(); } ImGui::End(); } +} + +UI::Actions drawMenu(config::client::options &options, state::state &state, const std::vector& packs) { + + drawOverlay(options.overlay, []{}); auto actions = drawCommon(options, state, packs); + const ImGuiIO &io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2, io.DisplaySize.y / 2), ImGuiCond_Always, ImVec2(.5f, .5f)); ImGui::Begin((std::string("Univerxel ") + UNIVERXEL_VERSION).c_str(), NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse); state.login.name.resize(128); @@ -327,33 +338,9 @@ UI::Actions drawInGame(config::client::options &options, state::state &state, co ImGui::End(); } - if (options.overlay.visible) { - if (options.overlay.corner != -1) { - ImVec2 window_pos = ImVec2((options.overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (options.overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN); - ImVec2 window_pos_pivot = ImVec2((options.overlay.corner & 1) ? 1.0f : 0.0f, (options.overlay.corner & 2) ? 1.0f : 0.0f); - ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); - } - ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background - ImGui::Begin("Overlay", &options.overlay.visible, (options.overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); - ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + drawOverlay(options.overlay, [&]{ ImGui::Text("%ld tris(%ld models)", reports.tris_count, reports.models_count); - if (ImGui::BeginPopupContextWindow()) { - if (ImGui::MenuItem("Custom", NULL, options.overlay.corner == -1)) - options.overlay.corner = -1; - if (ImGui::MenuItem("Top-left", NULL, options.overlay.corner == 0)) - options.overlay.corner = 0; - if (ImGui::MenuItem("Top-right", NULL, options.overlay.corner == 1)) - options.overlay.corner = 1; - if (ImGui::MenuItem("Bottom-left", NULL, options.overlay.corner == 2)) - options.overlay.corner = 2; - if (ImGui::MenuItem("Bottom-right", NULL, options.overlay.corner == 3)) - options.overlay.corner = 3; - if (options.overlay.visible && ImGui::MenuItem("Close")) - options.overlay.visible = false; - ImGui::EndPopup(); - } - ImGui::End(); - } + }); return actions; } diff --git a/src/client/srvContainer.hpp b/src/client/srvContainer.hpp index 568973d..a95115c 100644 --- a/src/client/srvContainer.hpp +++ b/src/client/srvContainer.hpp @@ -60,8 +60,8 @@ struct ServerFactory: public world::AbstractServerFactory { { int load = options.world.loadDistance; int keep = options.world.keepDistance; - if (ImGui::SliderInt("Load distance", &load, 1, options.world.keepDistance) | - ImGui::SliderInt("Keep distance", &keep, options.world.loadDistance + 1, 21)) { + if (ImGui::SliderInt("Load distance##srv", &load, 1, options.world.keepDistance) | + ImGui::SliderInt("Keep distance##srv", &keep, options.world.loadDistance + 1, 21)) { options.world.loadDistance = load; options.world.keepDistance = keep; } diff --git a/src/client/world/Chunk.hpp b/src/client/world/Chunk.hpp index a1c371a..ca902d9 100644 --- a/src/client/world/Chunk.hpp +++ b/src/client/world/Chunk.hpp @@ -39,10 +39,9 @@ class Chunk final: public world::EdittableChunk { public: Chunk(std::istream &is): EdittableChunk(new ChunkFutureEdits(this), is) { } /// Create from average - Chunk(Voxel val): EdittableChunk(new ChunkFutureEdits(this)), isAverage(true), isMajorant(val.swap()) { voxels.fill(Voxel(val.material(), val.density())); } - Chunk(): EdittableChunk(new ChunkFutureEdits(this)) { } + Chunk(Voxel val): EdittableChunk(new ChunkFutureEdits(this), Voxel(val.material(), val.density())), isMajorant(val.swap()) { } - constexpr bool isTrusted(bool allowMajorant) const { return !isAverage || (allowMajorant && isMajorant); } + inline bool isTrusted(bool allowMajorant) const noexcept { return isHeavy() || (allowMajorant && isMajorant); } void unsetMajorant() { assert(isMajorant); isMajorant = false; @@ -51,13 +50,11 @@ public: ChunkFutureEdits &setEdits() { assert(edits); return *(ChunkFutureEdits*)edits.get(); } private: - /// Is temporary average - const bool isAverage = false; /// Is temporary full valued bool isMajorant = false; }; -/// Chunk full of air -static const std::shared_ptr EMPTY_CHUNK = std::make_shared(); +/// Chunk full of void +static const std::shared_ptr EMPTY_CHUNK = std::make_shared(Voxel()); } \ No newline at end of file diff --git a/src/client/world/DistantUniverse.cpp b/src/client/world/DistantUniverse.cpp index 530f672..fe4fe37 100644 --- a/src/client/world/DistantUniverse.cpp +++ b/src/client/world/DistantUniverse.cpp @@ -351,7 +351,7 @@ bool DistantUniverse::onPacket(const memory::read_view& buf, net::PacketFlags) { } if (auto ck = readChunk()) { part->allocate(); - part->chunks.at(part->getIdx(pos.chunk)) = ck; + part->emplace(pos.chunk, ck); } break; } diff --git a/src/client/world/LocalUniverse.cpp b/src/client/world/LocalUniverse.cpp index 784658e..584ea32 100644 --- a/src/client/world/LocalUniverse.cpp +++ b/src/client/world/LocalUniverse.cpp @@ -42,6 +42,7 @@ void LocalUniverse::update(cell_pos pos, float deltaTime) { const auto lock = getElements(); const auto &elements = *lock; + const auto visibleDist2 = glm::pow2(contouring->getVisibleDist()); { // Update alive areas ZoneScopedN("World"); auto rng = std::mt19937(std::rand()); @@ -60,7 +61,8 @@ void LocalUniverse::update(cell_pos pos, float deltaTime) { //MAYBE: compute absolute manually from chunk_pos(1) * pivot const cell_pos chunkPos = area_tf.computeChild(glm::multiply(chunk.first)); const auto chunkDist = glm::length2(glm::divide(chunkPos - pos)); - //MAYBE: if in load range + if (chunkDist > visibleDist2) // FIXME: World still > 20ms with big contouring.keepDist + continue; if(const auto neighbors = chunk.second->setEdits()->update(deltaTime, rng() < contouringThreshold)) { contouring->onUpdate(area_chunk_pos{id, chunk.first}, chunkDist, chunks, neighbors.value()); diff --git a/src/core/geometry/glm.hpp b/src/core/geometry/glm.hpp index 606f836..30a5a87 100644 --- a/src/core/geometry/glm.hpp +++ b/src/core/geometry/glm.hpp @@ -16,7 +16,9 @@ namespace glm { typedef vec<3, uc> ucvec3; const auto IDX_LENGTH = 32; + const auto IDX_SHIFT = 5; const auto IDX_LENGTH2 = IDX_LENGTH * IDX_LENGTH; const auto IDX_SIZE = IDX_LENGTH2 * IDX_LENGTH; using idx = glm::u16; + const idx IDX_MASK = (1u< + constexpr U inline pow3(U v) { + return v * v * v; + } constexpr ll inline length2(const llvec3& v) { return pow2(v.x) + pow2(v.y) + pow2(v.z); } @@ -64,32 +68,43 @@ namespace glm { constexpr std::pair inline split(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) { return {divide(value, m), modulo(value, m)}; } + /// idx order is z-y-x constexpr ucvec3 inline fromIdx(idx idx) { assert(idx < IDX_SIZE); - return ucvec3(idx / IDX_LENGTH2, (idx / IDX_LENGTH) % IDX_LENGTH, idx % IDX_LENGTH); + return ucvec3(idx % IDX_LENGTH, (idx / IDX_LENGTH) % IDX_LENGTH, idx / IDX_LENGTH2); + } + template + constexpr ucvec3 inline fromIdxShift(idx idx) { + assert(idx < IDX_SIZE); + constexpr glm::idx MASK = (1u<> S) & MASK, (idx >> (2*S)) & MASK); } constexpr usvec3 inline fromIdx(size_t idx, lvec3 size) { assert(size.x>=0 && size.y>=0 && size.z>=0); assert((lvec3::value_type)idx < size.x * size.y * size.z); - return usvec3(idx / (size.y * size.z), (idx / size.z) % size.y, idx % size.z); + return usvec3(idx % size.x, (idx / size.x) % size.y, idx / (size.y * size.x)); + } + template + constexpr idx inline toIdxShift(glm::us x, glm::us y, glm::us z) { + return (z << (2*S)) | (y << S) | x; } constexpr idx inline toIdx(glm::uc x, glm::uc y, glm::uc z) { - return (x * IDX_LENGTH + y) * IDX_LENGTH + z; + return (z * IDX_LENGTH + y) * IDX_LENGTH + x; } - constexpr idx inline toIdx(glm::uc x, glm::uc y, glm::uc z, glm::uc sy, glm::uc sz) { - return x * sy * sz + y * sz + z; + constexpr idx inline toIdx(glm::uc x, glm::uc y, glm::uc z, glm::uc sy, glm::uc sx) { + return z * sy * sx + y * sx + x; } constexpr idx inline toIdx(ucvec3 pos) { return toIdx(pos.x, pos.y, pos.z); } constexpr idx inline toIdx(ucvec3 pos, ucvec3 size) { - return toIdx(pos.x, pos.y, pos.z, size.y, size.z); + return toIdx(pos.x, pos.y, pos.z, size.y, size.x); } constexpr l inline toIdx(lvec3 pos) { return toIdx(pos.x, pos.y, pos.z); } constexpr l inline toIdx(lvec3 pos, lvec3 size) { - return toIdx(pos.x, pos.y, pos.z, size.y, size.z); + return toIdx(pos.x, pos.y, pos.z, size.y, size.x); } constexpr std::pair inline splitIdx(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) { return {divide(value, m), toIdx(rem(value.x, m.x), rem(value.y, m.y), rem(value.z, m.z))}; diff --git a/src/core/utils/os.cpp b/src/core/utils/os.cpp new file mode 100644 index 0000000..3e9f395 --- /dev/null +++ b/src/core/utils/os.cpp @@ -0,0 +1,60 @@ +#include "os.hpp" + +#ifdef _WINDOWS +#include +#include + +os::global os::GetGlobal() { + global out{}; + + MEMORYSTATUSEX memInfo; + memInfo.dwLength = sizeof(MEMORYSTATUSEX); + GlobalMemoryStatusEx(&memInfo); + DWORDLONG totalVirtualMem = memInfo.ullTotalPageFile; + + out.ram.total = memInfo.ullTotalPhys; + out.ram.free = memInfo.ullAvailPhys + out.swap.total = memInfo.ullTotalPageFile; + out.swap.total -= out.ram.total; + out.swap.free = memInfo.ullAvailPageFile; + out.swap.free -= out.ram.free; + return out; +} + +os::process os::GetProcess() { + PROCESS_MEMORY_COUNTERS_EX pmc; + GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)); + process out{}; + out.memused = pmc.PrivateUsage; + return out; +} + +#else +#include +#include +#include + +os::global os::GetGlobal() { + struct sysinfo memInfo; + sysinfo(&memInfo); + global out{}; + out.ram.total = memInfo.totalram; + out.ram.total *= memInfo.mem_unit; + out.ram.free = memInfo.freeram; + out.ram.free *= memInfo.mem_unit; + out.swap.total = memInfo.totalswap; + out.swap.total *= memInfo.mem_unit; + out.swap.free = memInfo.freeswap; + out.swap.free *= memInfo.mem_unit; + return out; +} + +os::process os::GetProcess() { + struct rusage r_usage; + process out{}; + getrusage(RUSAGE_SELF, &r_usage); + out.memused = r_usage.ru_maxrss; + out.memused *= 1000; + return out; +} +#endif \ No newline at end of file diff --git a/src/core/utils/os.hpp b/src/core/utils/os.hpp new file mode 100644 index 0000000..b5248e7 --- /dev/null +++ b/src/core/utils/os.hpp @@ -0,0 +1,20 @@ +#pragma once + +namespace os { + + struct global { + struct memory { + /// In bytes + unsigned long long total; + unsigned long long free; + }; + memory ram; + memory swap; + }; + global GetGlobal(); + + struct process { + unsigned long long memused; + }; + process GetProcess(); +} \ No newline at end of file diff --git a/src/core/world/Chunk.cpp b/src/core/world/Chunk.cpp index 62c40f2..c495af0 100644 --- a/src/core/world/Chunk.cpp +++ b/src/core/world/Chunk.cpp @@ -5,8 +5,9 @@ using namespace world; -Chunk::Chunk(std::istream& str, bool rle) { - if(rle) { +Chunk::Chunk(std::istream& str) { + voxels.resize(CHUNK_SIZE); + if constexpr(RLE) { uint16_t i = 0; while(!str.eof()) { uint16_t count; @@ -19,13 +20,18 @@ Chunk::Chunk(std::istream& str, bool rle) { i++; } } - assert(i == CHUNK_SIZE && "Mismatch data length"); + assert((i == 1 || i == CHUNK_SIZE) && "Mismatch data length"); + if (i == 1) { + voxels.resize(1); + voxels.shrink_to_fit(); + } } else { for(auto& voxel: voxels) { str.read(reinterpret_cast(&voxel), sizeof(voxel)); } } } +Chunk::Chunk(Voxel v): voxels({v}) { } Chunk::~Chunk() { } const Voxel &Chunk::getAt(const chunk_voxel_pos &pos) const { diff --git a/src/core/world/Chunk.hpp b/src/core/world/Chunk.hpp index 2ab5592..a6d363f 100644 --- a/src/core/world/Chunk.hpp +++ b/src/core/world/Chunk.hpp @@ -6,22 +6,28 @@ namespace world { constexpr auto RLE = true; //NOTE: only ~2.5% gain after zstd - /// World part as linear 3d voxel array + /// World part as linear 3d voxel array or just a single one (32x LOD) class Chunk { public: - Chunk(std::istream& str, bool rle = RLE); + Chunk(std::istream& str); virtual ~Chunk(); /// Get voxel from index inline const Voxel& get(chunk_voxel_idx idx) const { - return voxels[idx]; + assert(isAllocated()); + return voxels[isHeavy() ? idx : 0]; } /// Get voxel from position const Voxel &getAt(const chunk_voxel_pos &pos) const; + inline bool isAllocated() const noexcept { return !voxels.empty(); } + inline bool isHeavy() const noexcept { return voxels.size() > 1; } + protected: + Chunk(Voxel v); + /// NOTE: voxels must be allocated by child constructor Chunk() { } /// Chunk data - std::array voxels; + std::vector voxels; //MAYBE: manual handle }; } \ No newline at end of file diff --git a/src/core/world/EdittableChunk.cpp b/src/core/world/EdittableChunk.cpp index e921fe0..592fc3f 100644 --- a/src/core/world/EdittableChunk.cpp +++ b/src/core/world/EdittableChunk.cpp @@ -6,16 +6,31 @@ using namespace world; -EdittableChunk::EdittableChunk(owner edits, std::istream& str, bool rle): world::Chunk(str, rle), edits(edits) { } +EdittableChunk::EdittableChunk(owner edits, std::istream& str): world::Chunk(str), edits(edits) { } +EdittableChunk::EdittableChunk(owner edits, Voxel v): world::Chunk(v), edits(edits) { } EdittableChunk::EdittableChunk(owner edits): world::Chunk(), edits(edits) { } EdittableChunk::~EdittableChunk() { } +void EdittableChunk::set(chunk_voxel_idx idx, const Voxel& voxel) { + if (!isHeavy()) { + if (get(idx).value == voxel.value) + return; + + flatten(); + } + voxels[idx] = voxel; +} void EdittableChunk::setAt(const chunk_voxel_pos &pos, const Voxel& voxel) { return set(glm::toIdx(pos), voxel); } +void EdittableChunk::flatten() { + assert(isAllocated()); + voxels.resize(CHUNK_SIZE, voxels.front()); +} + std::optional ChunkEdits::update(float deltaTime, bool animate) { - ZoneScopedN("Chunk"); + //ZoneScopedN("Chunk"); for(auto it = edits.begin(); it != edits.end();) { it->second.delay -= deltaTime; if(it->second.delay <= 0 && animate) { diff --git a/src/core/world/EdittableChunk.hpp b/src/core/world/EdittableChunk.hpp index e0832ac..24c5794 100644 --- a/src/core/world/EdittableChunk.hpp +++ b/src/core/world/EdittableChunk.hpp @@ -61,14 +61,21 @@ namespace world { const ChunkEdits& getEdits() const { assert(edits); return *edits.get(); } /// Direct set without update of voxel at index - inline void set(chunk_voxel_idx idx, const Voxel& voxel) { - voxels[idx] = voxel; - } + void set(chunk_voxel_idx idx, const Voxel &voxel); /// Direct set without update of voxel at position void setAt(const chunk_voxel_pos &pos, const Voxel& voxel); + /// Convert light to heavy + /// Faster to edit + void flatten(); + /// Convert heavy to light + /// Uses less memory + void shrink(); + protected: - EdittableChunk(owner, std::istream& str, bool rle = RLE); + EdittableChunk(owner, std::istream& str); + EdittableChunk(owner, Voxel); + /// NOTE: voxels must be allocated by child constructor EdittableChunk(owner); const std::unique_ptr edits; }; diff --git a/src/core/world/Node.hpp b/src/core/world/Node.hpp index 80e3b00..3e845b0 100644 --- a/src/core/world/Node.hpp +++ b/src/core/world/Node.hpp @@ -141,10 +141,18 @@ namespace world { assert(inRange(c)); const auto idx = getIdx(c); if (idx < chunks.size()) - return chunks.at(idx); + return chunks[idx]; return nullptr; } + void emplace(const chunk_pos& c, const std::shared_ptr& ck) { + allocate(); + chunks[getIdx(c)] = ck; + } + void emplace(const chunk_pos& c, std::shared_ptr&& ck) { + allocate(); + chunks[getIdx(c)] = std::move(ck); + } glm::usvec3 size; }; diff --git a/src/core/world/generator/Abstract.hpp b/src/core/world/generator/Abstract.hpp index adfc2ec..e5b405c 100644 --- a/src/core/world/generator/Abstract.hpp +++ b/src/core/world/generator/Abstract.hpp @@ -8,8 +8,11 @@ namespace world::generator { /// Abstract Noise generator class Abstract { public: + using voxels = std::vector; + /// Generate chunk voxels - virtual void generate(const chunk_pos &at, std::array &out) = 0; + /// level 5: upto 32^3 voxels + virtual void generate5(const voxel_pos &min, voxels &out) = 0; /// Get gravity vector at given point virtual glm::vec3 getGravity(const voxel_pos& point) const = 0; /// Area visual curvature @@ -22,8 +25,8 @@ namespace world::generator { struct Params { }; Void() = default; - void generate(const chunk_pos &, std::array &out) override { - out.fill(Voxel()); + void generate5(const voxel_pos &, voxels &out) override { + out = {Voxel()}; } glm::vec3 getGravity(const voxel_pos&) const override { return glm::vec3(0); } }; diff --git a/src/core/world/generator/Cave.hpp b/src/core/world/generator/Cave.hpp index bf4af39..e9c681e 100644 --- a/src/core/world/generator/Cave.hpp +++ b/src/core/world/generator/Cave.hpp @@ -6,6 +6,25 @@ namespace world::generator { /// Endless cave network class Cave: public Abstract { + private: + template + inline void generate(const voxel_pos& min, voxels& out) const { + constexpr uint32_t LENGTH = 1u << level; + constexpr size_t SIZE = glm::pow3(LENGTH); + constexpr auto VEC = glm::ivec3(LENGTH); + const auto densitySet = density.getRaw(min, VEC); + const auto materialSet = material.getRaw(min, VEC); + constexpr auto MATERIAL_SKIP = 2; + const int materialMax = module::Registry::Get()->getMaterials().names.size() - (MATERIAL_SKIP + 1); + out.resize(SIZE); + for (size_t i = 0; i < SIZE; i++) { + const auto density = std::clamp((densitySet.get()[i] + params.density) * params.granularity, 0.f, 1.f) * Voxel::DENSITY_MAX; + const auto material = density > 0 ? MATERIAL_SKIP + std::clamp(static_cast(std::lrint((materialSet.get()[i] + 1) / 2 * materialMax)), + 0, materialMax) : 1; //NOTE: map (approx -1, 1) to (1, mat_max) + out[i] = Voxel(material, density); + } + } + public: struct Params { Params(int seed = 42, float density = 0, float gran = 30): seed(seed), density(density), granularity(gran) { } @@ -19,17 +38,8 @@ namespace world::generator { }; Cave(const Params p): params(p), density(Noise::SimplexFractal(p.seed)), material(Noise::Cellular(p.seed * 5, .1)) { } - void generate(const chunk_pos &pos, std::array &out) override { - const auto densitySet = density.getBlock(pos, CHUNK_LENGTH); - const auto materialSet = material.getBlock(pos, CHUNK_LENGTH); - constexpr auto MATERIAL_SKIP = 2; - const int materialMax = module::Registry::Get()->getMaterials().names.size() - (MATERIAL_SKIP + 1); - for (size_t i = 0; i < CHUNK_SIZE; i++) { - const auto density = std::clamp((densitySet.get()[i] + params.density) * params.granularity, 0.f, 1.f) * Voxel::DENSITY_MAX; - const auto material = density > 0 ? MATERIAL_SKIP + std::clamp(static_cast(std::lrint((materialSet.get()[i] + 1) / 2 * materialMax)), - 0, materialMax) : 1; //NOTE: map (approx -1, 1) to (1, mat_max) - out[i] = Voxel(material, density); - } + void generate5(const voxel_pos& min, voxels& out) override { + generate<5>(min, out); } glm::vec3 getGravity(const voxel_pos&) const override { return glm::vec3(-1, 0, 0); } private: diff --git a/src/modules/core/PlanetGenerator.hpp b/src/modules/core/PlanetGenerator.hpp index 8fbe077..2a3627a 100644 --- a/src/modules/core/PlanetGenerator.hpp +++ b/src/modules/core/PlanetGenerator.hpp @@ -10,7 +10,54 @@ namespace world::generator { /// Abstract shaped planet generator template - class Planet: public Abstract { + class Planet final: public Abstract { + private: + template + inline void generate(const voxel_pos& min, voxels& out) const { + constexpr uint32_t LENGTH = 1u << level; + constexpr size_t SIZE = glm::pow3(LENGTH); + constexpr auto VEC = glm::i32vec3(LENGTH); + const auto densitySet = density.getRaw(min, VEC); + const auto displacementSet = displacement.getRaw(min, VEC); + const auto& IDS = world::module::core::Core::Get()->ids; + const auto genAt = [&](size_t i) -> Voxel { + const auto heightRatio = ((float)getHeight(min + voxel_pos(glm::fromIdx(i))) - params.height) / params.height; + + const auto verticalDensityOffset = heightRatio / (heightRatio >= 0 ? params.surface_roughness : params.depth_roughness); + const auto density = std::min((densitySet.get()[i] + params.density - verticalDensityOffset) * params.granularity, 1.f) * Voxel::DENSITY_MAX; + + if (density > 0) { + const auto material = [&]() -> int { + const auto noisedHeightRatio = heightRatio - displacementSet.get()[i] * params.beach_displacement; + if(noisedHeightRatio >= 0) { + return densitySet.get()[i] + params.density < ((heightRatio + 0.007f) / params.surface_roughness) ? IDS.GRASS : IDS.DIRT; + } else { + return noisedHeightRatio >= -params.beach_depth ? IDS.SAND : IDS.ROCK; + } + }(); + return Voxel(material, density); + } else { + if (heightRatio >= 0) { + return Voxel(IDS.AIR, (heightRatio < params.surface_roughness) * Voxel::DENSITY_MAX); + } else { + return Voxel(IDS.WATER, std::clamp(-heightRatio * params.height, 0.f, 1.f) * Voxel::DENSITY_MAX); + } + } + }; + + const auto sample = genAt(0); + bool unique = true; + out.resize(SIZE); + for (size_t i = 0; i < SIZE; i++) { + out[i] = genAt(i); + if (unique && out[i].value != sample.value) + unique = false; + } + if (unique) { + out = voxels{sample}; + } + } + public: struct Params: Cave::Params { Params(voxel_pos::value_type height, int seed = 42, float surface_roughness = .1, float depth_roughness = .05, float density = 0, float gran = 30): @@ -29,39 +76,8 @@ namespace world::generator { }; Planet(const Params& p) : params(p), density(Noise::SimplexFractal(p.seed)), displacement(Noise::Simplex(p.seed * 5, .01)) {} - void generate(const chunk_pos &pos, std::array &out) override { - auto densitySet = density.getBlock(pos, CHUNK_LENGTH); - auto displacementSet = displacement.getBlock(pos, CHUNK_LENGTH); - const auto& IDS = world::module::core::Core::Get()->ids; - for (size_t i = 0; i < CHUNK_SIZE; i++) { - const auto vPos = glm::fromIdx(i); - const auto noise_i = glm::toIdx(vPos.z, vPos.y, vPos.x); - - const auto heightRatio = ((float)getHeight(glm::multiply(pos) + glm::llvec3(vPos)) - params.height) / params.height; - - const auto verticalDensityOffset = heightRatio / (heightRatio >= 0 ? params.surface_roughness : params.depth_roughness); - const auto density = std::min((densitySet.get()[noise_i] + params.density - verticalDensityOffset) * params.granularity, 1.f) * Voxel::DENSITY_MAX; - - out[i] = [&]() -> Voxel { - if (density > 0) { - const auto material = [&]() -> int { - const auto noisedHeightRatio = heightRatio - displacementSet.get()[noise_i] * params.beach_displacement; - if(noisedHeightRatio >= 0) { - return densitySet.get()[noise_i] + params.density < ((heightRatio + 0.007f) / params.surface_roughness) ? IDS.GRASS : IDS.DIRT; - } else { - return noisedHeightRatio >= -params.beach_depth ? IDS.SAND : IDS.ROCK; - } - }(); - return Voxel(material, density); - } else { - if (heightRatio >= 0) { - return Voxel(IDS.AIR, (heightRatio < params.surface_roughness) * Voxel::DENSITY_MAX); - } else { - return Voxel(IDS.WATER, std::clamp(-heightRatio * params.height, 0.f, 1.f) * Voxel::DENSITY_MAX); - } - } - }(); - } + void generate5(const voxel_pos &min, voxels &out) override { + generate<5>(min, out); } glm::vec3 getGravity(const voxel_pos& pos) const override { const auto heightRatio = static_cast((getHeight(pos) - params.height) / params.height); diff --git a/src/server/world/Chunk.cpp b/src/server/world/Chunk.cpp index dea1a84..2ac55d8 100644 --- a/src/server/world/Chunk.cpp +++ b/src/server/world/Chunk.cpp @@ -7,14 +7,14 @@ using namespace world::server; -Chunk::Chunk(owner edits): world::EdittableChunk(edits) { } +Chunk::Chunk(owner edits): world::EdittableChunk(edits, Voxel(0)) { } Chunk::Chunk(owner edits, const chunk_pos &pos, const std::unique_ptr &rnd): world::EdittableChunk(edits) { - rnd->generate(pos, voxels); + rnd->generate5(glm::multiply(pos), voxels); } -Chunk::Chunk(owner edits, std::istream& str, bool rle): world::EdittableChunk(edits, str, rle) { } +Chunk::Chunk(owner edits, std::istream& str): world::EdittableChunk(edits, str) { } Chunk::~Chunk() { } -world::Voxel Chunk::write(std::ostream& str, bool rle) const { +world::Voxel Chunk::write(std::ostream& str) const { Voxel::material_t majMat = UINT16_MAX; uint16_t majCounter = 1; size_t visibleDensity = 0; @@ -30,7 +30,7 @@ world::Voxel Chunk::write(std::ostream& str, bool rle) const { } visibleDensity += current.density() * current.is_visible(); }; - if (rle) { + if constexpr(RLE) { auto it = voxels.begin(); uint16_t counter = 1; Voxel current = *it; @@ -63,14 +63,21 @@ world::Voxel Chunk::write(std::ostream& str, bool rle) const { } void Chunk::set(uint16_t idx, const world::Voxel& val) { - modified = modified || (voxels[idx].value != val.value); + const auto same = get(idx).value == val.value; + if (!isHeavy()) { + if (same) + return; + + flatten(); + } + modified = modified || !same; voxels[idx] = val; } void Chunk::setAt(const chunk_voxel_pos& pos, const world::Voxel& val) { set(glm::toIdx(pos), val); } std::optional Chunk::replace(chunk_voxel_idx idx, const world::Voxel& val, float) { - const auto res = voxels[idx]; + const auto res = get(idx); set(idx, val); return {world::Item{res.density(), res.material()}}; } diff --git a/src/server/world/Chunk.hpp b/src/server/world/Chunk.hpp index a11d184..986b738 100644 --- a/src/server/world/Chunk.hpp +++ b/src/server/world/Chunk.hpp @@ -13,7 +13,7 @@ namespace world::server { public: Chunk(owner); Chunk(owner, const chunk_pos &pos, const std::unique_ptr &rnd); - Chunk(owner, std::istream& str, bool rle = RLE); + Chunk(owner, std::istream& str); virtual ~Chunk(); /// Set voxel from index @@ -28,7 +28,7 @@ namespace world::server { inline bool isModified() const { return modified; } /// Write to file and return average (majorant material) /// Voxel swap bit indicate perfect majority - Voxel write(std::ostream& str, bool rle = RLE) const; + Voxel write(std::ostream& str) const; private: /// Modified by player @@ -36,8 +36,11 @@ namespace world::server { }; struct ChunkFactory { + /// Dynamic chunk using rnd virtual std::shared_ptr create(const chunk_pos &pos, const std::unique_ptr &rnd) const = 0; + /// Dynamic chunk from stream virtual std::shared_ptr create(std::istream &str) const = 0; + /// Light void chunk virtual std::shared_ptr create() const = 0; }; diff --git a/src/server/world/Region.cpp b/src/server/world/Region.cpp index 5f17661..e1a1564 100644 --- a/src/server/world/Region.cpp +++ b/src/server/world/Region.cpp @@ -9,7 +9,7 @@ using namespace world::server; #define REMOVE_CORRUPTED 1 -#define LAZYNESS 8 +Region::save_throttler::save_throttler(): nextSaveExponent(2) { } Region::Region(const std::string &folderPath, const area_region_ref &id) { path = folderPath + '/' + std::to_string(id.area.val) + '.' + std::to_string(id.region.x) + '.' + @@ -19,7 +19,7 @@ Region::Region(const std::string &folderPath, const area_region_ref &id) { } Region::~Region() { if(!content.empty()) - save(changed); + save(saveThrottler.isUnsaved()); } size_t Region::Read(const std::string &path, const std::function &, std::vector &&)>& out) { @@ -197,13 +197,13 @@ void Region::write(const region_chunk_pos& pos, const zstd::write_ctx& ctx, cons } else { content.emplace(pos, node(avg, std::move(buffer))); } - changed = true; + saveThrottler.change(); } save(false); } void Region::save(bool force) { - if(!force && rand() % LAZYNESS == 0) + if(!(force || saveThrottler.mustSave())) return; std::unique_lock lock(mutex); @@ -260,5 +260,5 @@ void Region::save(bool force) { return; } file.close(); - changed = false; + saveThrottler.save(); } \ No newline at end of file diff --git a/src/server/world/Region.hpp b/src/server/world/Region.hpp index 7da06de..44f0f8f 100644 --- a/src/server/world/Region.hpp +++ b/src/server/world/Region.hpp @@ -54,7 +54,23 @@ namespace world::server { std::vector data; }; robin_hood::unordered_map content; - bool changed = false; + + struct save_throttler { + save_throttler(); + /// Counter of currently unsaved + size_t changes = 0; + /// Save when changes > (1 << it) + /// MAYBE: decrease with time + size_t nextSaveExponent; + + inline void change() { changes++; } + inline constexpr bool isUnsaved() const { return changes > 0; } + inline constexpr bool mustSave() const { return changes > (1ull << nextSaveExponent); } + inline void save() { + changes = 0; + nextSaveExponent++; + } + } saveThrottler; void load(); }; diff --git a/src/server/world/SharedChunk.hpp b/src/server/world/SharedChunk.hpp index 91a0e42..fc85020 100644 --- a/src/server/world/SharedChunk.hpp +++ b/src/server/world/SharedChunk.hpp @@ -9,13 +9,13 @@ namespace world::server { public: SharedChunk(): Chunk(new world::ChunkEdits(this)) { } SharedChunk(const chunk_pos &pos, const std::unique_ptr &rnd): Chunk(new world::ChunkEdits(this), pos, rnd) { } - SharedChunk(std::istream &str, bool rle = RLE): Chunk(new world::ChunkEdits(this), str, rle) { } + SharedChunk(std::istream &str): Chunk(new world::ChunkEdits(this), str) { } /// Break voxel std::optional replace(chunk_voxel_idx idx, const Voxel &val, float delay = 0) override { - const auto res = voxels[idx]; + const auto res = get(idx); set(idx, val); - if (res.value != voxels[idx].value) { + if (res.value != get(idx).value) { setEdits()->add(world::ChunkEdits::Edit{res, delay, idx}); } return {Item{res.density(), res.material()}}; diff --git a/src/server/world/Universe.cpp b/src/server/world/Universe.cpp index 539104a..3e8e338 100644 --- a/src/server/world/Universe.cpp +++ b/src/server/world/Universe.cpp @@ -370,14 +370,13 @@ void Universe::upgrade() { { // Store loaded chunks ZoneScopedN("Load"); - loadedQueue.extractor([&](const std::pair> &loaded) { + loadedQueue.extractor([&](std::pair> &loaded) { if (Elements::Is(loaded.first.node)) { if (const auto area = elements.findArea(loaded.first.node.val)) - area->get()->setChunks().emplace(loaded.first.chunk, loaded.second); + area->get()->setChunks().emplace(loaded.first.chunk, std::move(loaded.second)); } else { - if (const auto part = elements.findPart(loaded.first.node.val)) { - part->get()->chunks[part->get()->getIdx(loaded.first.chunk)] = loaded.second; - } + if (const auto part = elements.findPart(loaded.first.node.val)) + part->get()->emplace(loaded.first.chunk, std::move(loaded.second)); } }); } @@ -404,12 +403,12 @@ void Universe::loadIndex() { elements->createInstance(Elements::start_point(p.val, transform(glm::vec3(1000, 0, 0))), generational::id(0)); //TODO: generate universe { - const auto radius = 1 << 4; - generator::RoundPlanet::Params opts(radius * CHUNK_LENGTH * 3 / 4); + const auto radius = 1 << 6; + const auto surface = radius * CHUNK_LENGTH * 3 / 4; + generator::RoundPlanet::Params opts(surface); Area::params params{radius, 0, std::vector(sizeof(opts))}; memcpy(params.params.data(), &opts, params.params.size()); - const auto a = elements->createArea(Elements::start_point(generational::id(), transform(glm::multiply(voxel_pos(radius, 0, 0)), glm::angleAxis(.5f, glm::vec3(0, 1, 0))), velocity(offset_pos(), glm::angleAxis(.1f, glm::vec3(0, 1, 0)))), params); - elements->createInstance(Elements::start_point(a.val, transform(glm::vec3(-radius * CHUNK_LENGTH + 100, 0, 0))), generational::id(0)); + elements->createArea(Elements::start_point(generational::id(), transform(world_pos(-surface, 0, 0)), velocity(offset_pos(), glm::angleAxis(.01f, glm::vec3(0, 1, 0)))), params); } } index.close();