diff --git a/dev/tests/example.lua b/dev/tests/example.lua index c066c1ed..3dbe1197 100644 --- a/dev/tests/example.lua +++ b/dev/tests/example.lua @@ -8,11 +8,8 @@ assert(player.get_name(pid) == "Xerxes") test.sleep_until(function() return world.count_chunks() >= 9 end, 1000) print(world.count_chunks()) -for i=1,3 do - print("---") - timeit(1000000, block.get, 0, 0, 0) - timeit(1000000, block.get_slow, 0, 0, 0) -end +timeit(10000000, block.get, 0, 0, 0) +timeit(10000000, core.blank, 0, 0, 0) block.destruct(0, 0, 0, pid) assert(block.get(0, 0, 0) == 0) diff --git a/src/constants.hpp b/src/constants.hpp index c8de5a8d..6f4a6ce6 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -35,6 +35,11 @@ inline constexpr int CHUNK_D = 16; inline constexpr uint VOXEL_USER_BITS = 8; inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS; +/// @brief % unordered map max average buckets load factor. +/// Low value gives significant performance impact by minimizing collisions and +/// lookup latency. Default value (1.0) shows x2 slower work. +inline constexpr float CHUNKS_MAP_MAX_LOAD_FACTOR = 0.1f; + /// @brief chunk volume (count of voxels per Chunk) inline constexpr int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D); diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index dbd87489..5d681e5f 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -8,6 +8,7 @@ #include "voxels/Chunks.hpp" #include "voxels/voxel.hpp" #include "voxels/GlobalChunks.hpp" +#include "voxels/blocks_agent.hpp" #include "world/Level.hpp" #include "maths/voxmaths.hpp" #include "data/StructLayout.hpp" @@ -15,13 +16,13 @@ using namespace scripting; -static const Block* require_block(lua::State* L) { +static inline const Block* require_block(lua::State* L) { auto indices = content->getIndices(); auto id = lua::tointeger(L, 1); return indices->blocks.get(id); } -static int l_get_def(lua::State* L) { +static inline int l_get_def(lua::State* L) { if (auto def = require_block(L)) { return lua::pushstring(L, def->name); } @@ -39,8 +40,7 @@ static int l_is_solid_at(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - - return lua::pushboolean(L, level->chunks->isSolidBlock(x, y, z)); + return lua::pushboolean(L, blocks_agent::is_solid_at(*level->chunks, x, y, z)); } static int l_count(lua::State* L) { @@ -110,16 +110,7 @@ static int l_get(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - auto vox = level->chunks->get(x, y, z); - int id = vox == nullptr ? -1 : vox->id; - return lua::pushinteger(L, id); -} - -static int l_get_slow(lua::State* L) { - auto x = lua::tointeger(L, 1); - auto y = lua::tointeger(L, 2); - auto z = lua::tointeger(L, 3); - auto vox = level->chunksStorage->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); int id = vox == nullptr ? -1 : vox->id; return lua::pushinteger(L, id); } @@ -128,7 +119,7 @@ static int l_get_x(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - auto vox = level->chunksStorage->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); if (vox == nullptr) { return lua::pushivec_stack(L, glm::ivec3(1, 0, 0)); } @@ -145,7 +136,7 @@ static int l_get_y(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - auto vox = level->chunksStorage->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); if (vox == nullptr) { return lua::pushivec_stack(L, glm::ivec3(0, 1, 0)); } @@ -162,7 +153,7 @@ static int l_get_z(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - auto vox = level->chunksStorage->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); if (vox == nullptr) { return lua::pushivec_stack(L, glm::ivec3(0, 0, 1)); } @@ -179,7 +170,7 @@ static int l_get_rotation(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - voxel* vox = level->chunksStorage->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); int rotation = vox == nullptr ? 0 : vox->state.rotation; return lua::pushinteger(L, rotation); } @@ -197,7 +188,7 @@ static int l_get_states(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - auto vox = level->chunksStorage->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); int states = vox == nullptr ? 0 : blockstate2int(vox->state); return lua::pushinteger(L, states); } @@ -226,7 +217,7 @@ static int l_get_user_bits(lua::State* L) { auto offset = lua::tointeger(L, 4) + VOXEL_USER_BITS_OFFSET; auto bits = lua::tointeger(L, 5); - auto vox = level->chunks->get(x, y, z); + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); if (vox == nullptr) { return lua::pushinteger(L, 0); } @@ -352,7 +343,7 @@ static int l_place(lua::State* L) { if (static_cast(id) >= indices->blocks.count()) { return 0; } - if (!level->chunks->get(x, y, z)) { + if (!blocks_agent::get(*level->chunksStorage, x, y, z)) { return 0; } const auto def = level->content->getIndices()->blocks.get(id); @@ -373,11 +364,11 @@ static int l_destruct(lua::State* L) { auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); auto playerid = lua::gettop(L) >= 4 ? lua::tointeger(L, 4) : -1; - auto voxel = level->chunks->get(x, y, z); - if (voxel == nullptr) { + auto vox = blocks_agent::get(*level->chunksStorage, x, y, z); + if (vox == nullptr) { return 0; } - auto& def = level->content->getIndices()->blocks.require(voxel->id); + auto& def = level->content->getIndices()->blocks.require(vox->id); auto player = level->players->get(playerid); controller->getBlocksController()->breakBlock(player, def, x, y, z); return 0; @@ -504,12 +495,15 @@ static int l_get_field(lua::State* L) { } auto cx = floordiv(x, CHUNK_W); auto cz = floordiv(z, CHUNK_D); - auto chunk = level->chunks->getChunk(cx, cz); + auto chunk = blocks_agent::get_chunk(*level->chunksStorage, cx, cz); + if (chunk == nullptr || y < 0 || y >= CHUNK_H) { + return 0; + } auto lx = x - cx * CHUNK_W; auto lz = z - cz * CHUNK_W; size_t voxelIndex = vox_index(lx, y, lz); - const auto& vox = level->chunks->require(x, y, z); + const auto& vox = chunk->voxels[voxelIndex]; const auto& def = content->getIndices()->blocks.require(vox.id); if (def.dataStruct == nullptr) { return 0; @@ -568,15 +562,18 @@ static int l_set_field(lua::State* L) { if (lua::gettop(L) >= 6) { index = lua::tointeger(L, 6); } - auto vox = level->chunks->get(x, y, z); auto cx = floordiv(x, CHUNK_W); auto cz = floordiv(z, CHUNK_D); - auto chunk = level->chunks->getChunk(cx, cz); auto lx = x - cx * CHUNK_W; auto lz = z - cz * CHUNK_W; + auto chunk = blocks_agent::get_chunk(*level->chunksStorage, cx, cz); + if (chunk == nullptr || y < 0 || y >= CHUNK_H) { + return 0; + } size_t voxelIndex = vox_index(lx, y, lz); + const auto& vox = chunk->voxels[voxelIndex]; - const auto& def = content->getIndices()->blocks.require(vox->id); + const auto& def = content->getIndices()->blocks.require(vox.id); if (def.dataStruct == nullptr) { return 0; } @@ -608,7 +605,6 @@ const luaL_Reg blocklib[] = { {"is_replaceable_at", lua::wrap}, {"set", lua::wrap}, {"get", lua::wrap}, - {"get_slow", lua::wrap}, {"get_X", lua::wrap}, {"get_Y", lua::wrap}, {"get_Z", lua::wrap}, diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 5ac9d93d..d2d0c80f 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -16,6 +16,7 @@ #include "logic/EngineController.hpp" #include "logic/LevelController.hpp" #include "util/listutil.hpp" +#include "util/platform.hpp" #include "window/Events.hpp" #include "window/Window.hpp" #include "world/Level.hpp" @@ -220,8 +221,6 @@ static int l_load_texture(lua::State* L) { return 0; } -#include "util/platform.hpp" - static int l_open_folder(lua::State* L) { auto path = engine->getPaths()->resolve(lua::require_string(L, 1)); platform::open_folder(path); @@ -239,7 +238,7 @@ static int l_blank(lua::State*) { } const luaL_Reg corelib[] = { - {"nop", lua::wrap}, + {"blank", lua::wrap}, {"get_version", lua::wrap}, {"new_world", lua::wrap}, {"open_world", lua::wrap}, diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index 390ac16c..8bfe6166 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -23,6 +23,7 @@ #include "Block.hpp" #include "Chunk.hpp" #include "voxel.hpp" +#include "blocks_agent.hpp" Chunks::Chunks( int32_t w, @@ -33,7 +34,7 @@ Chunks::Chunks( Level* level ) : level(level), - indices(level->content->getIndices()), + indices(level ? level->content->getIndices() : nullptr), areaMap(w, d), worldFiles(wfile) { areaMap.setCenter(ox-w/2, oz-d/2); @@ -43,30 +44,11 @@ Chunks::Chunks( } voxel* Chunks::get(int32_t x, int32_t y, int32_t z) const { - if (y < 0 || y >= CHUNK_H) { - return nullptr; - } - int cx = floordiv(x); - int cz = floordiv(z); - auto ptr = areaMap.getIf(cx, cz); - if (ptr == nullptr) { - return nullptr; - } - Chunk* chunk = ptr->get(); - if (chunk == nullptr) { - return nullptr; - } - int lx = x - cx * CHUNK_W; - int lz = z - cz * CHUNK_D; - return &chunk->voxels[(y * CHUNK_D + lz) * CHUNK_W + lx]; + return blocks_agent::get(*this, x, y, z); } voxel& Chunks::require(int32_t x, int32_t y, int32_t z) const { - auto voxel = get(x, y, z); - if (voxel == nullptr) { - throw std::runtime_error("voxel does not exist"); - } - return *voxel; + return blocks_agent::require(*this, x, y, z); } const AABB* Chunks::isObstacleAt(float x, float y, float z) const { @@ -103,9 +85,7 @@ const AABB* Chunks::isObstacleAt(float x, float y, float z) const { } bool Chunks::isSolidBlock(int32_t x, int32_t y, int32_t z) { - voxel* v = get(x, y, z); - if (v == nullptr) return false; - return indices->blocks.require(v->id).rt.solid; + return blocks_agent::is_solid_at(*this, x, y, z); } bool Chunks::isReplaceableBlock(int32_t x, int32_t y, int32_t z) { diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index fd33eb9a..a39c8960 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -152,4 +152,8 @@ public: size_t getVolume() const { return areaMap.area(); } + + const ContentIndices& getContentIndices() const { + return *indices; + } }; diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 379ca328..38843398 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -16,10 +16,10 @@ #include "Block.hpp" #include "Chunk.hpp" -inline long long keyfrom(int x, int z) { +inline uint64_t keyfrom(int32_t x, int32_t z) { union { - int pos[2]; - long long key; + int32_t pos[2]; + uint64_t key; } ekey; ekey.pos[0] = x; ekey.pos[1] = z; @@ -28,7 +28,9 @@ inline long long keyfrom(int x, int z) { static debug::Logger logger("chunks-storage"); -GlobalChunks::GlobalChunks(Level* level) : level(level) { +GlobalChunks::GlobalChunks(Level* level) + : level(level), indices(level ? level->content->getIndices() : nullptr) { + chunksMap.max_load_factor(CHUNKS_MAP_MAX_LOAD_FACTOR); } std::shared_ptr GlobalChunks::fetch(int x, int z) { @@ -67,6 +69,29 @@ void GlobalChunks::erase(int x, int z) { chunksMap.erase(keyfrom(x, z)); } +static inline auto load_inventories( + WorldRegions& regions, + const Chunk& chunk, + const ContentUnitIndices& defs +) { + auto invs = regions.fetchInventories(chunk.x, chunk.z); + auto iterator = invs.begin(); + while (iterator != invs.end()) { + uint index = iterator->first; + const auto& def = defs.require(chunk.voxels[index].id); + if (def.inventorySize == 0) { + iterator = invs.erase(iterator); + continue; + } + auto& inventory = iterator->second; + if (def.inventorySize != inventory->size()) { + inventory->resize(def.inventorySize); + } + ++iterator; + } + return invs; +} + std::shared_ptr GlobalChunks::create(int x, int z) { const auto& found = chunksMap.find(keyfrom(x, z)); if (found != chunksMap.end()) { @@ -84,22 +109,10 @@ std::shared_ptr GlobalChunks::create(int x, int z) { chunk->decode(data.get()); check_voxels(indices, *chunk); - auto invs = regions.fetchInventories(chunk->x, chunk->z); - auto iterator = invs.begin(); - while (iterator != invs.end()) { - uint index = iterator->first; - const auto& def = indices.blocks.require(chunk->voxels[index].id); - if (def.inventorySize == 0) { - iterator = invs.erase(iterator); - continue; - } - auto& inventory = iterator->second; - if (def.inventorySize != inventory->size()) { - inventory->resize(def.inventorySize); - } - ++iterator; - } - chunk->setBlockInventories(std::move(invs)); + + chunk->setBlockInventories( + load_inventories(regions, *chunk, indices.blocks) + ); auto entitiesData = regions.fetchEntities(chunk->x, chunk->z); if (entitiesData.getType() == dv::value_type::object) { @@ -132,24 +145,6 @@ size_t GlobalChunks::size() const { return chunksMap.size(); } -voxel* GlobalChunks::get(int x, int y, int z) const { - if (y < 0 || y >= CHUNK_H) { - return nullptr; - } - - int cx = floordiv(x); - int cz = floordiv(z); - - const auto& found = chunksMap.find(keyfrom(cx, cz)); - if (found == chunksMap.end()) { - return nullptr; - } - const auto& chunk = found->second; - int lx = x - cx * CHUNK_W; - int lz = z - cz * CHUNK_D; - return &chunk->voxels[(y * CHUNK_D + lz) * CHUNK_W + lx]; -} - void GlobalChunks::incref(Chunk* chunk) { auto key = reinterpret_cast(chunk); const auto& found = refCounters.find(key); @@ -209,3 +204,15 @@ void GlobalChunks::saveAll() { save(chunk.get()); } } + +void GlobalChunks::putChunk(std::shared_ptr chunk) { + chunksMap[keyfrom(chunk->x, chunk->z)] = std::move(chunk); +} + +Chunk* GlobalChunks::getChunk(int cx, int cz) const { + const auto& found = chunksMap.find(keyfrom(cx, cz)); + if (found == chunksMap.end()) { + return nullptr; + } + return found->second.get(); +} diff --git a/src/voxels/GlobalChunks.hpp b/src/voxels/GlobalChunks.hpp index b21e15e6..f7ce0e92 100644 --- a/src/voxels/GlobalChunks.hpp +++ b/src/voxels/GlobalChunks.hpp @@ -11,10 +11,12 @@ class Chunk; class Level; +class ContentIndices; class GlobalChunks { Level* level; - std::unordered_map> chunksMap; + const ContentIndices* indices; + std::unordered_map> chunksMap; std::unordered_map> pinnedChunks; std::unordered_map refCounters; public: @@ -27,8 +29,6 @@ public: void pinChunk(std::shared_ptr chunk); void unpinChunk(int x, int z); - voxel* get(int x, int y, int z) const; - size_t size() const; void incref(Chunk* chunk); @@ -38,4 +38,12 @@ public: void save(Chunk* chunk); void saveAll(); + + void putChunk(std::shared_ptr chunk); + + Chunk* getChunk(int cx, int cz) const; + + const ContentIndices& getContentIndices() const { + return *indices; + } }; diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp new file mode 100644 index 00000000..31ae79c5 --- /dev/null +++ b/src/voxels/blocks_agent.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "voxel.hpp" +#include "typedefs.hpp" +#include "maths/voxmaths.hpp" + +class Chunk; +class Chunks; +class GlobalChunks; + +/// Using templates to minimize OOP overhead + +namespace blocks_agent { + +template +inline Chunk* get_chunk(const Storage& chunks, int cx, int cz) { + return chunks.getChunk(cx, cz); +} + +template +inline voxel* get(const Storage& chunks, int32_t x, int32_t y, int32_t z) { + if (y < 0 || y >= CHUNK_H) { + return nullptr; + } + int cx = floordiv(x); + int cz = floordiv(z); + Chunk* chunk = get_chunk(chunks, cx, cz); + if (chunk == nullptr) { + return nullptr; + } + int lx = x - cx * CHUNK_W; + int lz = z - cz * CHUNK_D; + return &chunk->voxels[(y * CHUNK_D + lz) * CHUNK_W + lx]; +} + +template +inline voxel& require(const Storage& chunks, int32_t x, int32_t y, int32_t z) { + auto vox = get(chunks, x, y, z); + if (vox == nullptr) { + throw std::runtime_error("voxel does not exist"); + } + return *vox; +} + +template +inline const Block& get_block_def(const Storage& chunks, blockid_t id) { + return chunks.getContentIndices().blocks.require(id); +} + +template +inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { + if (auto vox = get(chunks, x, y, z)) { + return get_block_def(chunks, vox->id).rt.solid; + } + return false; +} + +} // blocks_agent