From 8a3c96372164f162fad8faca748ee80efd814ce9 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sat, 30 Sep 2023 22:54:47 +0200 Subject: [PATCH] solve some memory leaks regarding thread communication queues also temporarly move chunk update in primary thread --- CMakeLists.txt | 4 +- include/chunk.hpp | 26 ++--- include/chunkmanager.hpp | 7 +- src/chunk.cpp | 6 +- src/chunkmanager.cpp | 212 +++++++++++++++++++-------------------- src/chunkmesher.cpp | 8 +- src/debugwindow.cpp | 8 +- src/main.cpp | 12 +-- src/renderer.cpp | 5 +- 9 files changed, 147 insertions(+), 141 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de73763..0371b51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 3.2) project(cmake-project-template) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3") -#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g") set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) diff --git a/include/chunk.hpp b/include/chunk.hpp index 17f29fb..368ee4e 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -23,13 +23,16 @@ namespace Chunk { - constexpr uint8_t CHUNK_STATE_GENERATED = 1; - constexpr uint8_t CHUNK_STATE_MESHED = 2; - constexpr uint8_t CHUNK_STATE_MESH_LOADED = 4; - constexpr uint8_t CHUNK_STATE_LOADED = 8; - constexpr uint8_t CHUNK_STATE_OUTOFVISION = 16; - constexpr uint8_t CHUNK_STATE_UNLOADED = 32; - constexpr uint8_t CHUNK_STATE_EMPTY = 64; + constexpr uint16_t CHUNK_STATE_GENERATED = 1; + constexpr uint16_t CHUNK_STATE_MESHED = 2; + constexpr uint16_t CHUNK_STATE_MESH_LOADED = 4; + constexpr uint16_t CHUNK_STATE_LOADED = 8; + constexpr uint16_t CHUNK_STATE_OUTOFVISION = 16; + constexpr uint16_t CHUNK_STATE_UNLOADED = 32; + constexpr uint16_t CHUNK_STATE_EMPTY = 64; + constexpr uint16_t CHUNK_STATE_IN_GENERATION_QUEUE = 128; + constexpr uint16_t CHUNK_STATE_IN_MESHING_QUEUE = 256; + constexpr uint16_t CHUNK_STATE_IN_RENDERING_QUEUE = 512; int coord3DTo1D(int x, int y, int z); @@ -38,16 +41,15 @@ namespace Chunk public: Chunk(glm::vec3 pos = glm::vec3(0.0f)); // a default value for the argument satisfies the need for a default constructor when using the type in an unordered_map (i.e. in chunkmanager) - ~Chunk(); public: void createBuffers(); void deleteBuffers(); glm::vec3 getPosition() { return this->position; } - uint8_t getTotalState() { return this->state; } - bool getState(uint8_t n) { return (this->state & n) == n; } - void setState(uint8_t nstate, bool value); + uint16_t getTotalState() { return this->state; } + bool getState(uint16_t n) { return (this->state & n) == n; } + void setState(uint16_t nstate, bool value); void setBlock(Block b, int x, int y, int z); void setBlocks(int start, int end, Block b); @@ -63,7 +65,7 @@ namespace Chunk glm::vec3 position{}; IntervalMap blocks{}; - std::atomic_uint8_t state{0}; + std::atomic_uint16_t state{0}; }; }; diff --git a/include/chunkmanager.hpp b/include/chunkmanager.hpp index 688e376..7615859 100644 --- a/include/chunkmanager.hpp +++ b/include/chunkmanager.hpp @@ -6,12 +6,14 @@ #include #include #include + +#include #include #include "globals.hpp" // Seconds to be passed outside of render distance for a chunk to be destroyed -#define UNLOAD_TIMEOUT 0 +#define UNLOAD_TIMEOUT 5 #define MESHING_PRIORITY_NORMAL 0 #define MESHING_PRIORITY_PLAYER_EDIT 10 @@ -19,7 +21,8 @@ namespace chunkmanager { - typedef oneapi::tbb::concurrent_hash_map ChunkTable; + //typedef oneapi::tbb::concurrent_hash_map ChunkTable; + typedef std::unordered_map ChunkTable; typedef std::pair ChunkPQEntry; // The comparing function to use struct compare_f { diff --git a/src/chunk.cpp b/src/chunk.cpp index b14fffc..fad1fb7 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -22,10 +22,6 @@ namespace Chunk this->setBlocks(0, CHUNK_MAX_INDEX, Block::AIR); } - Chunk ::~Chunk() - { - } - void Chunk::createBuffers(){ glGenVertexArrays(1, &(this->VAO)); glGenBuffers(1, &(this->VBO)); @@ -59,7 +55,7 @@ namespace Chunk this->blocks.insert(start < 0 ? 0 : start, end >= CHUNK_VOLUME ? CHUNK_VOLUME : end, b); } - void Chunk::setState(uint8_t nstate, bool value) + void Chunk::setState(uint16_t nstate, bool value) { if (value) this->state.fetch_or(nstate); diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index e8cb1d6..fa93d84 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -39,6 +39,11 @@ namespace chunkmanager ChunkPriorityQueue chunks_to_mesh_queue; int block_to_place{2}; + + // MEMORYTEST + bool populated{false}; + bool populated2{false}; + float start_time{0}; // Init chunkmanager. Chunk indices and start threads int chunks_volume_real; @@ -68,14 +73,21 @@ namespace chunkmanager mesh_thread = std::thread(mesh); debug::window::set_parameter("block_type_return", &block_to_place); + + // MEMORYTEST + start_time = glfwGetTime(); } // Method for world generation thread(s) void generate(){ while(should_run){ ChunkPQEntry entry; - while(chunks_to_generate_queue.try_pop(entry)) generateChunk(entry.first); + if(chunks_to_generate_queue.try_pop(entry)){ + generateChunk(entry.first); + entry.first->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false); + } } + chunks_to_generate_queue.clear(); } // Method for chunk meshing thread(s) @@ -86,144 +98,119 @@ namespace chunkmanager Chunk::Chunk* chunk = entry.first; if(chunk->getState(Chunk::CHUNK_STATE_GENERATED)){ chunkmesher::mesh(chunk); + entry.first->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false); } } } + chunks_to_mesh_queue.clear(); } - oneapi::tbb::concurrent_queue chunks_todelete; - oneapi::tbb::concurrent_queue chunks_primary_delete; int nUnloaded{0}; - int already{0}; - bool first{true}; - void update(){ - } + std::queue chunks_todelete; void primary_thread_update(){ + int nExplored{0}, nMeshed{0}, nGenerated{0}; int chunkX=static_cast(theCamera.getAtomicPosX() / CHUNK_SIZE); int chunkY=static_cast(theCamera.getAtomicPosY() / CHUNK_SIZE); int chunkZ=static_cast(theCamera.getAtomicPosZ() / CHUNK_SIZE); - int explored{0}; - // Eventually create new chunks + debug::window::set_parameter("update_chunks_tobedeleted", (int) chunks_todelete.size()); + while(!chunks_todelete.empty()){ + int a = chunks_todelete.front(); + auto i = chunks.find(a); + if(chunks.erase(a)){ + delete i->second; + nUnloaded++; + } + else + std::cout << "no such element found to delete\n"; + chunks_todelete.pop(); + + } + // Eventually create new chunks near the player for(int i = 0; i < chunks_volume; i++) { const int16_t x = chunks_indices[i][0] + chunkX; const int16_t y = chunks_indices[i][1] + chunkY; const int16_t z = chunks_indices[i][2] + chunkZ; if(x < 0 || y < 0 || z < 0 || x > 1023 || y > 1023 || z > 1023) continue; + nExplored++; + + const int32_t index = calculateIndex(x, y, z); - explored++; - ChunkTable::accessor a; - if(chunks.count(index) == 0){ - chunks.emplace(a, std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z)))); - a.release(); - }else{ - if(first){ - already++; - std::cout << "index already present: " << index << std::endl; - std::cout << x << "," << y << "," << z << "(" << chunks_indices[i][1] << std::endl; - } - } + if(chunks.find(index) == chunks.end()) chunks.emplace(std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z)))); } - first= false; - if(already) std::cout << "chunks already present " << already << std::endl; - debug::window::set_parameter("update_chunks_explored", explored); - debug::window::set_parameter("px", theCamera.getAtomicPosX()); - debug::window::set_parameter("py", theCamera.getAtomicPosY()); - debug::window::set_parameter("pz", theCamera.getAtomicPosZ()); - debug::window::set_parameter("cx", chunkX); - debug::window::set_parameter("cy", chunkY); - debug::window::set_parameter("cz", chunkZ); - debug::window::set_parameter("lx", theCamera.getFront().x); - debug::window::set_parameter("ly", theCamera.getFront().y); - debug::window::set_parameter("lz", theCamera.getFront().z); + for(auto a = chunks.begin(); a != chunks.end(); a++){ + Chunk::Chunk* c = a->second; + int x = c->getPosition().x; + int y = c->getPosition().y; + int z = c->getPosition().z; + int distx = x - chunkX; + int disty = y - chunkY; + int distz = z - chunkZ; - debug::window::set_parameter("update_chunks_total", (int) (chunks.size())); + if( + distx >= -RENDER_DISTANCE && distx <= RENDER_DISTANCE && + disty >= -RENDER_DISTANCE && disty <= RENDER_DISTANCE && + distz >= -RENDER_DISTANCE && distz <= RENDER_DISTANCE + ){ - // Perform needed operations on all the chunks - oneapi::tbb::parallel_for(chunks.range(), [=](ChunkTable::range_type &r){ - for(ChunkTable::const_iterator a = r.begin(); a != r.end(); a++){ - if(a->second->getState(Chunk::CHUNK_STATE_UNLOADED)){ - if(a->second->getState(Chunk::CHUNK_STATE_MESH_LOADED)) chunks_todelete.push(a->second); - //nUnloaded++; - continue; - } + // If within distance + // Reset out-of-view flags + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); - int distx = a->second->getPosition().x - chunkX; - int disty = a->second->getPosition().y - chunkY; - int distz = a->second->getPosition().z - chunkZ; - if(distx >= -RENDER_DISTANCE && distx < RENDER_DISTANCE && - disty >= -RENDER_DISTANCE && disty < RENDER_DISTANCE && - distz >= -RENDER_DISTANCE && distz < RENDER_DISTANCE){ - - // reset out-of-vision and unload flags - a->second->setState(Chunk::CHUNK_STATE_OUTOFVISION, false); - a->second->setState(Chunk::CHUNK_STATE_UNLOADED, false); - - if(! (a->second->getState(Chunk::CHUNK_STATE_GENERATED))) { - chunks_to_generate_queue.push(std::make_pair(a->second, GENERATION_PRIORITY_NORMAL)); - }else if(! (a->second->getState(Chunk::CHUNK_STATE_MESHED))){ - int x = a->second->getPosition().x; - int y = a->second->getPosition().y; - int z = a->second->getPosition().z; - - ChunkTable::const_accessor a1; - if( - (x + 1 > 1023 || (chunks.find(a1, calculateIndex(x+1, y, z)) && - a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) && - (x - 1 < 0|| (chunks.find(a1, calculateIndex(x-1, y, z)) && - a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) && - (y + 1 > 1023 || (chunks.find(a1, calculateIndex(x, y+1, z)) && - a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) && - (y - 1 < 0|| (chunks.find(a1, calculateIndex(x, y-1, z)) && - a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) && - (z + 1 > 1023 || (chunks.find(a1, calculateIndex(x, y, z+1)) && - a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) && - (z - 1 < 0|| (chunks.find(a1, calculateIndex(x, y, z-1)) && - a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) - ) - chunks_to_mesh_queue.push(std::make_pair(a->second, MESHING_PRIORITY_NORMAL)); - }else{ - renderer::getChunksToRender().push(a->second); + // If not yet generated + if(!c->getState(Chunk::CHUNK_STATE_GENERATED)){ + if(!c->getState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE)){ + // Generate + chunks_to_generate_queue.push(std::make_pair(c, GENERATION_PRIORITY_NORMAL)); + c->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, true); } }else{ - if(a->second->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ - // If chunk was already out and enough time has passed - if(glfwGetTime() - a->second->unload_timer > UNLOAD_TIMEOUT){ - // Mark the chunk to be unloaded - a->second->setState(Chunk::CHUNK_STATE_UNLOADED, true); + nGenerated++; + // If generated but not yet meshed + // TODO: not getting meshed + if(!c->getState(Chunk::CHUNK_STATE_MESHED)){ + if(!c->getState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE)){ + // Mesh + chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_NORMAL)); + c->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, true); } - } else{ - // Mark has out of vision and annotate when it started - a->second->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); - a->second->setState(Chunk::CHUNK_STATE_UNLOADED, false); - a->second->unload_timer = glfwGetTime(); + }else{ + nMeshed++; + // If generated & meshed, render + /*if(!c->getState(Chunk::CHUNK_STATE_IN_RENDERING_QUEUE)){ + renderer::getChunksToRender().push(c); + c->setState(Chunk::CHUNK_STATE_IN_RENDERING_QUEUE, true); + }*/ } } + + }else{ + // If not within distance + if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ + // If enough time has passed, set to be deleted + if(glfwGetTime() - c->unload_timer >= UNLOAD_TIMEOUT){ + chunks_todelete.push(calculateIndex(x,y,z)); + } + }else{ + // Mark as out of view, and start waiting time + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); + c->unload_timer = glfwGetTime(); + } } - }); - - Chunk::Chunk* n; - ChunkTable::accessor a; - while(chunks_todelete.try_pop(n)){ - int x = static_cast(n->getPosition().x); - int y = static_cast(n->getPosition().y); - int z = static_cast(n->getPosition().z); - if(x > 1023 || y > 1023 || z > 1023) continue; - const uint32_t index = calculateIndex(x, y, z); - - //std::cout << n->getState(Chunk::CHUNK_STATE_GENERATED) << "\n"; - if(chunks.erase(index)){ - //n->deleteBuffers(); - delete n; - }else - std::cout << "failed to free chunk at" << glm::to_string(n->getPosition()) << - std::endl; } + + debug::window::set_parameter("update_chunks_total", (int) chunks.size()); + debug::window::set_parameter("update_chunks_buckets", (int) chunks.bucket_count()); debug::window::set_parameter("update_chunks_freed", nUnloaded); + debug::window::set_parameter("update_chunks_generated", nGenerated); + debug::window::set_parameter("update_chunks_meshed", nMeshed); + debug::window::set_parameter("update_chunks_explored", nExplored); } // uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1023). There's actually two spare bits @@ -231,18 +218,23 @@ namespace chunkmanager return i | (j << 10) | (k << 20); } - oneapi::tbb::concurrent_queue& getDeleteVector(){ return chunks_todelete; } std::array, chunks_volume>& getChunksIndices(){ return chunks_indices; } void stop() { should_run=false; - //update_thread.join(); + + std::cout << "waiting for secondary threads to finish\n"; gen_thread.join(); + std::cout << "generation thread terminated\n"; mesh_thread.join(); + std::cout << "meshing thread terminated\n"; + } + void destroy(){ } + /* void blockpick(bool place){ // cast a ray from the camera in the direction pointed by the camera itself glm::vec3 pos = glm::vec3(theCamera.getAtomicPosX(), theCamera.getAtomicPosY(), @@ -360,5 +352,5 @@ namespace chunkmanager //std::cout << "Block is at " << bx << "," << by << "," << bz << "(" << (int)b << ")\n"; return b; } - } + }*/ }; diff --git a/src/chunkmesher.cpp b/src/chunkmesher.cpp index d87a6e2..f0d3898 100755 --- a/src/chunkmesher.cpp +++ b/src/chunkmesher.cpp @@ -98,6 +98,8 @@ void mesh(Chunk::Chunk* chunk) Block b1, b2; if(x[dim] >= 0) b1 = blocks[HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]]; else{ + b1 = Block::NULLBLK; + /* int cx = chunk->getPosition().x*CHUNK_SIZE; int cy = chunk->getPosition().y*CHUNK_SIZE; int cz = chunk->getPosition().z*CHUNK_SIZE; @@ -106,12 +108,14 @@ void mesh(Chunk::Chunk* chunk) int by = cy+x[1]; int bz = cz+x[2]; - b1 = chunkmanager::getBlockAtPos(bx, by, bz); + b1 = chunkmanager::getBlockAtPos(bx, by, bz);*/ } if(x[dim] < CHUNK_SIZE - 1) b2 = blocks[HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1] + q[1]][x[2] + q[2]]]; else{ + b2 = Block::NULLBLK; + /* int cx = chunk->getPosition().x*CHUNK_SIZE; int cy = chunk->getPosition().y*CHUNK_SIZE; int cz = chunk->getPosition().z*CHUNK_SIZE; @@ -120,7 +124,7 @@ void mesh(Chunk::Chunk* chunk) int by = cy+x[1] + q[1]; int bz = cz+x[2] + q[2]; - b2 = chunkmanager::getBlockAtPos(bx, by, bz); + b2 = chunkmanager::getBlockAtPos(bx, by, bz);*/ } // Compute the mask diff --git a/src/debugwindow.cpp b/src/debugwindow.cpp index d9a7228..61add27 100644 --- a/src/debugwindow.cpp +++ b/src/debugwindow.cpp @@ -115,7 +115,13 @@ namespace debug{ if(ImGui::CollapsingHeader("Chunks")){ ImGui::Text("Total chunks present: %d", std::any_cast(parameters.at("update_chunks_total"))); - ImGui::Text("Chunks freed from memory: %d", + ImGui::Text("Buckets in hash map: %d", + std::any_cast(parameters.at("update_chunks_buckets"))); + ImGui::Text("Chunks generated: %d", + std::any_cast(parameters.at("update_chunks_generated"))); + ImGui::Text("Chunks meshed: %d", + std::any_cast(parameters.at("update_chunks_meshed"))); + ImGui::Text("Chunks actually freed from memory: %d", std::any_cast(parameters.at("update_chunks_freed"))); ImGui::Text("Chunks explored: %d", std::any_cast(parameters.at("update_chunks_explored"))); diff --git a/src/main.cpp b/src/main.cpp index cfdff9b..fd7ed51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,11 +103,11 @@ int main() // Reset blockping timeout if 200ms have passed if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false; + chunkmanager::primary_thread_update(); + // Render pass renderer::render(); - chunkmanager::primary_thread_update(); - // Swap buffers to avoid tearing glfwSwapBuffers(window); glfwPollEvents(); @@ -117,9 +117,9 @@ int main() chunkmanager::stop(); // Cleanup allocated memory - chunkmanager::destroy(); - renderer::destroy(); debug::window::destroy(); + renderer::destroy(); + chunkmanager::destroy(); glfwTerminate(); return 0; @@ -143,13 +143,13 @@ void processInput(GLFWwindow *window) glfwSetWindowShouldClose(window, true); if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){ - chunkmanager::blockpick(false); + //chunkmanager::blockpick(false); blockpick=true; lastBlockPick=glfwGetTime(); } if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){ - chunkmanager::blockpick(true); + //chunkmanager::blockpick(true); blockpick=true; lastBlockPick=glfwGetTime(); } diff --git a/src/renderer.cpp b/src/renderer.cpp index c9dd381..9d59543 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -148,7 +148,7 @@ namespace renderer{ Chunk::Chunk* c; while(chunks_torender.try_pop(c)){ - if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue; + if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) goto end; total++; @@ -192,6 +192,9 @@ namespace renderer{ toGpu++; } } + +end: + c->setState(Chunk::CHUNK_STATE_IN_RENDERING_QUEUE, false); } debug::window::set_parameter("render_chunks_total", total);