mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
refactor(world): rework chunk state machine and player chunk tracking
Introduce ChunkState enum and ChunkEntity struct to manage chunk lifecycle. Store chunks as shared_ptr to avoid move operations during generation. Add clear_unused_chunks to remove chunks not referenced by any player. Implement deferred chunk request queue for safe processing after generation completes. Update player chunk set during required chunk computation. Improve thread safety with mutexes on chunk and player maps. Fix m_gening flag not reset after generation and add assertions for correctness. Change need_gen to require a player UUID, removing std::optional. Add chunk_size query method for debugging.
This commit is contained in:
@@ -123,6 +123,9 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
protobuf::libprotobuf
|
protobuf::libprotobuf
|
||||||
absl::log
|
absl::log
|
||||||
absl::check
|
absl::check
|
||||||
|
absl::base
|
||||||
|
absl::strings
|
||||||
|
absl::flat_hash_map
|
||||||
zstd::zstd
|
zstd::zstd
|
||||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||||
|
|
||||||
|
|||||||
@@ -93,4 +93,5 @@ private:
|
|||||||
// Generate biome-specific vegetation/structures
|
// Generate biome-specific vegetation/structures
|
||||||
void gen_phase_five();
|
void gen_phase_five();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
|
|||||||
@@ -5,16 +5,26 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_set>
|
||||||
namespace Cubed {
|
namespace Cubed {
|
||||||
class ServerWorld;
|
class ServerWorld;
|
||||||
class Session;
|
class Session;
|
||||||
class ServerPlayer {
|
class ServerPlayer {
|
||||||
|
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
ServerPlayer(const ServerPlayer&) = delete;
|
||||||
|
ServerPlayer(ServerPlayer&&) = delete;
|
||||||
|
ServerPlayer& operator=(const ServerPlayer&) = delete;
|
||||||
|
ServerPlayer& operator=(ServerPlayer&&) = delete;
|
||||||
ServerPlayer(std::string_view name, std::string_view uuid,
|
ServerPlayer(std::string_view name, std::string_view uuid,
|
||||||
ServerWorld& m_world, std::shared_ptr<Session> session,
|
ServerWorld& m_world, std::shared_ptr<Session> session,
|
||||||
TickType gametick);
|
TickType gametick);
|
||||||
|
using PlayerChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||||
|
|
||||||
const glm::vec3& get_pos() const;
|
const glm::vec3& get_pos() const;
|
||||||
const std::string& get_name() const;
|
const std::string& get_name() const;
|
||||||
const std::string& get_uuid() const;
|
const std::string& get_uuid() const;
|
||||||
@@ -24,6 +34,8 @@ public:
|
|||||||
bool is_disconnect(TickType current_gametick) const;
|
bool is_disconnect(TickType current_gametick) const;
|
||||||
int task_id() const;
|
int task_id() const;
|
||||||
void task_id(int id);
|
void task_id(int id);
|
||||||
|
bool has_player(ChunkPos pos) const;
|
||||||
|
void update_chunk_set(const ChunkPosSet& set);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr TickType TIMEOUT = 200;
|
static constexpr TickType TIMEOUT = 200;
|
||||||
@@ -35,5 +47,7 @@ private:
|
|||||||
std::shared_ptr<Session> m_session;
|
std::shared_ptr<Session> m_session;
|
||||||
std::atomic<TickType> m_last_gametick{0};
|
std::atomic<TickType> m_last_gametick{0};
|
||||||
std::atomic<int> m_chunk_task_id{0};
|
std::atomic<int> m_chunk_task_id{0};
|
||||||
|
mutable std::shared_mutex m_chunk_pos_mutex;
|
||||||
|
PlayerChunkPosSet m_player_chunk_pos_set;
|
||||||
};
|
};
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <future>
|
#include <future>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <tbb/concurrent_hash_map.h>
|
#include <tbb/concurrent_hash_map.h>
|
||||||
|
#include <tbb/concurrent_queue.h>
|
||||||
#include <tbb/concurrent_unordered_map.h>
|
#include <tbb/concurrent_unordered_map.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@@ -24,10 +25,9 @@ class ServerWorld {
|
|||||||
public:
|
public:
|
||||||
ServerWorld();
|
ServerWorld();
|
||||||
~ServerWorld();
|
~ServerWorld();
|
||||||
void player_join(std::string_view name, std::string_view uuid);
|
|
||||||
void handle_player_exit(const std::string& uuid);
|
void handle_player_exit(const std::string& uuid);
|
||||||
void init_world();
|
void init_world();
|
||||||
void need_gen(std::optional<std::string> uuid);
|
void need_gen(std::string uuid);
|
||||||
void update();
|
void update();
|
||||||
void hot_reload();
|
void hot_reload();
|
||||||
|
|
||||||
@@ -74,6 +74,9 @@ public:
|
|||||||
|
|
||||||
void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos);
|
void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos);
|
||||||
void handle_block_change(const BlockChangeReq& req);
|
void handle_block_change(const BlockChangeReq& req);
|
||||||
|
|
||||||
|
int chunk_size() const;
|
||||||
|
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||||
m_timers.emplace(std::piecewise_construct,
|
m_timers.emplace(std::piecewise_construct,
|
||||||
@@ -82,25 +85,42 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE };
|
||||||
|
struct ChunkEntity {
|
||||||
|
ChunkState state;
|
||||||
|
std::shared_ptr<ServerChunk> chunk;
|
||||||
|
};
|
||||||
|
|
||||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||||
|
struct PendingRequest {
|
||||||
|
std::string uuid;
|
||||||
|
int task_id;
|
||||||
|
ChunkPos pos;
|
||||||
|
};
|
||||||
struct PendingChunk {
|
struct PendingChunk {
|
||||||
ServerChunk chunk;
|
ServerChunk chunk;
|
||||||
std::future<void> future;
|
std::future<void> future;
|
||||||
};
|
};
|
||||||
|
struct FinishedChunk {
|
||||||
|
ChunkPos pos;
|
||||||
|
std::shared_ptr<ServerChunk> chunk;
|
||||||
|
};
|
||||||
|
|
||||||
using ChunkHashMap =
|
using ChunkHashMap =
|
||||||
tbb::concurrent_unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>;
|
std::unordered_map<ChunkPos, ChunkEntity, ChunkPos::Hash>;
|
||||||
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
|
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
|
||||||
using PendingChunkHashMap =
|
using PendingChunkHashMap =
|
||||||
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
|
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
|
||||||
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||||
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
|
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
|
||||||
|
|
||||||
using uuid_acc = PlayerUUIDMap::accessor;
|
using uuid_acc = PlayerUUIDMap::accessor;
|
||||||
using uuid_cacc = PlayerUUIDMap::const_accessor;
|
using uuid_cacc = PlayerUUIDMap::const_accessor;
|
||||||
// key = uuid
|
// key = uuid
|
||||||
PlayerHashMap m_players;
|
PlayerHashMap m_players;
|
||||||
ChunkHashMap m_chunks;
|
ChunkHashMap m_chunks;
|
||||||
PendingChunkHashMap m_new_chunks;
|
PendingChunkHashMap m_new_chunks;
|
||||||
std::vector<std::pair<ChunkPos, ServerChunk>> m_new_finished_chunk;
|
std::vector<FinishedChunk> m_new_finished_chunk;
|
||||||
|
|
||||||
CaveCarver m_cave_carcer;
|
CaveCarver m_cave_carcer;
|
||||||
RiverWorm m_river_worm;
|
RiverWorm m_river_worm;
|
||||||
@@ -136,19 +156,26 @@ private:
|
|||||||
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};
|
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};
|
||||||
|
|
||||||
PlayerUUIDMap m_uuid_to_name;
|
PlayerUUIDMap m_uuid_to_name;
|
||||||
|
|
||||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||||
|
tbb::concurrent_queue<PendingRequest> m_waiting_chunk_requests;
|
||||||
|
|
||||||
void init_chunks();
|
void init_chunks();
|
||||||
|
|
||||||
void gen_chunks_internal(std::optional<std::string> uuid);
|
void gen_chunks_internal(const std::string& uuid);
|
||||||
|
|
||||||
void compute_required_chunks(ChunkPosSet& required_chunks,
|
void compute_required_chunks(ChunkPosSet& required_chunks,
|
||||||
const std::optional<std::string>& uuid);
|
const std::optional<std::string>& uuid);
|
||||||
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
|
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
|
||||||
const ChunkPosSet&);
|
const ChunkPosSet&);
|
||||||
void submit_new_chunks(const std::optional<std::string>& uuid);
|
void submit_new_chunks(const std::string& uuid);
|
||||||
void poll_finished_chunks();
|
void poll_finished_chunks();
|
||||||
void wait_all_chunk_tasks();
|
void wait_all_chunk_tasks();
|
||||||
|
|
||||||
|
void clear_unused_chunks();
|
||||||
|
|
||||||
void send_time();
|
void send_time();
|
||||||
|
|
||||||
|
void send_chunk(int task_id, const std::string& uuid, ChunkPos pos);
|
||||||
};
|
};
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
|
|||||||
@@ -530,7 +530,7 @@ void DevPanel::show_server_world_table_bar() {
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Request Chunk Build")) {
|
if (ImGui::Button("Request Chunk Build")) {
|
||||||
Logger::warn("This Request Chunk Build button is not finish");
|
Logger::warn("This Request Chunk Build button is not finish");
|
||||||
m_app.server_world().need_gen(std::nullopt);
|
m_app.server_world().need_gen(m_player->get_uuid());
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
|
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
|
||||||
@@ -540,6 +540,7 @@ void DevPanel::show_server_world_table_bar() {
|
|||||||
m_app.server_world().stop_gen_thread();
|
m_app.server_world().stop_gen_thread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ImGui::Text("Server Chunk Size %d", m_app.server_world().chunk_size());
|
||||||
|
|
||||||
if (ImGui::BeginTabBar("World Settings")) {
|
if (ImGui::BeginTabBar("World Settings")) {
|
||||||
if (ImGui::BeginTabItem("Time")) {
|
if (ImGui::BeginTabItem("Time")) {
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ ServerChunk::ServerChunk(ServerChunk&& other) noexcept
|
|||||||
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
|
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
|
||||||
m_blocks(std::move(other.m_blocks)),
|
m_blocks(std::move(other.m_blocks)),
|
||||||
m_neightbor_blocks(std::move(other.m_neightbor_blocks)),
|
m_neightbor_blocks(std::move(other.m_neightbor_blocks)),
|
||||||
m_seed(other.m_seed), m_conditions(other.m_conditions) {}
|
m_seed(other.m_seed), m_conditions(other.m_conditions) {
|
||||||
|
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
|
||||||
|
}
|
||||||
|
|
||||||
ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
|
ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
|
||||||
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
|
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
|
||||||
@@ -22,6 +24,7 @@ ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
|
|||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
|
||||||
m_chunk_pos = std::move(other.m_chunk_pos);
|
m_chunk_pos = std::move(other.m_chunk_pos);
|
||||||
m_heightmap = std::move(other.m_heightmap);
|
m_heightmap = std::move(other.m_heightmap);
|
||||||
m_blocks = std::move(other.m_blocks);
|
m_blocks = std::move(other.m_blocks);
|
||||||
@@ -145,10 +148,13 @@ void ServerChunk::gen_chunk() {
|
|||||||
if (m_gening.exchange(true))
|
if (m_gening.exchange(true))
|
||||||
return;
|
return;
|
||||||
m_gening = true;
|
m_gening = true;
|
||||||
|
ASSERT_MSG(m_blocks.empty(),
|
||||||
|
"Blocks isn't Empty, chunk already generated!");
|
||||||
if (m_blocks.size() != 0) {
|
if (m_blocks.size() != 0) {
|
||||||
Logger::warn(
|
Logger::warn(
|
||||||
"Request Generator Chunk {} {} ,but the Blocks size is Not 0",
|
"Request Generator Chunk {} {} ,but the Blocks size is Not 0",
|
||||||
m_chunk_pos.x, m_chunk_pos.z);
|
m_chunk_pos.x, m_chunk_pos.z);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
std::vector<ServerChunk> neighbor;
|
std::vector<ServerChunk> neighbor;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
@@ -169,6 +175,7 @@ void ServerChunk::gen_chunk() {
|
|||||||
}
|
}
|
||||||
gen_phase_four(m_neightbor_blocks);
|
gen_phase_four(m_neightbor_blocks);
|
||||||
gen_phase_five();
|
gen_phase_five();
|
||||||
|
m_gening = false;
|
||||||
}
|
}
|
||||||
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
|
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,14 @@ bool ServerPlayer::is_disconnect(TickType current_gametick) const {
|
|||||||
int ServerPlayer::task_id() const { return m_chunk_task_id.load(); }
|
int ServerPlayer::task_id() const { return m_chunk_task_id.load(); }
|
||||||
void ServerPlayer::task_id(int id) { m_chunk_task_id = id; }
|
void ServerPlayer::task_id(int id) { m_chunk_task_id = id; }
|
||||||
|
|
||||||
|
bool ServerPlayer::has_player(ChunkPos pos) const {
|
||||||
|
std::shared_lock lock(m_chunk_pos_mutex);
|
||||||
|
return m_player_chunk_pos_set.find(pos) != m_player_chunk_pos_set.end();
|
||||||
|
}
|
||||||
|
void ServerPlayer::update_chunk_set(const ChunkPosSet& set) {
|
||||||
|
std::lock_guard lock(m_chunk_pos_mutex);
|
||||||
|
m_player_chunk_pos_set.clear();
|
||||||
|
m_player_chunk_pos_set.insert(set.begin(), set.end());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Cubed/tools/log.hpp"
|
#include "Cubed/tools/log.hpp"
|
||||||
#include "Cubed/tools/uuid.hpp"
|
#include "Cubed/tools/uuid.hpp"
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
@@ -32,10 +33,37 @@ ServerWorld::~ServerWorld() {
|
|||||||
void ServerWorld::wait_all_chunk_tasks() {
|
void ServerWorld::wait_all_chunk_tasks() {
|
||||||
std::lock_guard lock(m_new_chunk_mutex);
|
std::lock_guard lock(m_new_chunk_mutex);
|
||||||
for (auto& [pos, task] : m_new_chunks) {
|
for (auto& [pos, task] : m_new_chunks) {
|
||||||
task.future.get();
|
if (task.future.valid()) {
|
||||||
|
try {
|
||||||
|
task.future.get();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Logger::error("Chunk generation failed: {}", e.what());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger::error("Chunk {} {} not started gen task", pos.x, pos.z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerWorld::clear_unused_chunks() {
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_chunks_mutex, m_player_mutex);
|
||||||
|
Logger::info("before {}", m_chunks.size());
|
||||||
|
|
||||||
|
size_t removed = std::erase_if(m_chunks, [this](const auto& item) {
|
||||||
|
const auto& [pos, chunk] = item;
|
||||||
|
for (const auto& [uuid, player] : m_players) {
|
||||||
|
if (player.has_player(pos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
Logger::info("removed: {}", removed);
|
||||||
|
Logger::info("after {}", m_chunks.size());
|
||||||
|
}
|
||||||
|
|
||||||
void ServerWorld::send_time() {
|
void ServerWorld::send_time() {
|
||||||
Arena arena;
|
Arena arena;
|
||||||
auto* rsp = Arena::Create<UpdateTime>(&arena);
|
auto* rsp = Arena::Create<UpdateTime>(&arena);
|
||||||
@@ -48,6 +76,84 @@ void ServerWorld::send_time() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerWorld::send_chunk(int task_id, const std::string& uuid,
|
||||||
|
ChunkPos pos) {
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(m_player_mutex);
|
||||||
|
auto it = m_players.find(uuid);
|
||||||
|
if (it == m_players.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (task_id < it->second.task_id()) {
|
||||||
|
// Old chunk requests are simply discarded
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena arean;
|
||||||
|
ChunkDataRsp* rsp = Arena::Create<ChunkDataRsp>(&arean);
|
||||||
|
auto* rsq_pos = rsp->mutable_pos();
|
||||||
|
rsq_pos->set_x(pos.x);
|
||||||
|
rsq_pos->set_z(pos.z);
|
||||||
|
{
|
||||||
|
std::shared_lock lock(m_chunks_mutex);
|
||||||
|
auto it = m_chunks.find(pos);
|
||||||
|
if (it == m_chunks.end()) {
|
||||||
|
// No chunk found and not generating
|
||||||
|
Logger::error("Chunk {} {} neither pending nor ready", pos.x,
|
||||||
|
pos.z);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->second.state == ChunkState::GENERATING) {
|
||||||
|
|
||||||
|
m_waiting_chunk_requests.emplace(uuid, task_id, pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rsp->set_chunk_seed(it->second.chunk->seed());
|
||||||
|
rsp->set_biome_type(std::to_underlying(it->second.chunk->biome()));
|
||||||
|
auto* blocks = rsp->mutable_chunk_blocks();
|
||||||
|
auto& chunk_blocks = it->second.chunk->get_chunk_blocks();
|
||||||
|
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
|
||||||
|
auto& neighbor_blocks = it->second.chunk->get_neightbor_blocks();
|
||||||
|
auto assign = [](auto* nb,
|
||||||
|
const std::optional<std::vector<BlockType>>& blocks) {
|
||||||
|
if (!blocks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!nb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nb->Assign(blocks->begin(), blocks->end());
|
||||||
|
};
|
||||||
|
auto* nb1 = rsp->mutable_neighbor_blocks_1();
|
||||||
|
auto* nb2 = rsp->mutable_neighbor_blocks_2();
|
||||||
|
auto* nb3 = rsp->mutable_neighbor_blocks_3();
|
||||||
|
auto* nb4 = rsp->mutable_neighbor_blocks_4();
|
||||||
|
assign(nb1, neighbor_blocks[0]);
|
||||||
|
assign(nb2, neighbor_blocks[1]);
|
||||||
|
assign(nb3, neighbor_blocks[2]);
|
||||||
|
assign(nb4, neighbor_blocks[3]);
|
||||||
|
}
|
||||||
|
std::shared_ptr<Session> s;
|
||||||
|
{
|
||||||
|
std::shared_lock lock(m_player_mutex);
|
||||||
|
auto it = m_players.find(uuid);
|
||||||
|
if (it != m_players.end()) {
|
||||||
|
s = it->second.get_session();
|
||||||
|
it->second.update_sync_gametick(m_game_ticks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!s) {
|
||||||
|
Logger::error("Player {} session not exist", uuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rsp->set_task_id(task_id);
|
||||||
|
s->send(make_packet(*rsp));
|
||||||
|
}
|
||||||
|
|
||||||
void ServerWorld::init_world() {
|
void ServerWorld::init_world() {
|
||||||
|
|
||||||
register_timer("player disconnect", 5, [this]() {
|
register_timer("player disconnect", 5, [this]() {
|
||||||
@@ -64,6 +170,13 @@ void ServerWorld::init_world() {
|
|||||||
handle_player_exit(uuid);
|
handle_player_exit(uuid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Periodically process pending players
|
||||||
|
register_timer("player chunk send", 1, [this]() {
|
||||||
|
PendingRequest request;
|
||||||
|
if (m_waiting_chunk_requests.try_pop(request)) {
|
||||||
|
handle_chunk_req(request.task_id, request.uuid, request.pos);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
m_cave_carcer.init(ChunkGenerator::seed());
|
m_cave_carcer.init(ChunkGenerator::seed());
|
||||||
m_river_worm.init(ChunkGenerator::seed());
|
m_river_worm.init(ChunkGenerator::seed());
|
||||||
@@ -84,27 +197,35 @@ void ServerWorld::init_world() {
|
|||||||
|
|
||||||
void ServerWorld::init_chunks() { hot_reload(); }
|
void ServerWorld::init_chunks() { hot_reload(); }
|
||||||
|
|
||||||
void ServerWorld::gen_chunks_internal(std::optional<std::string> uuid) {
|
void ServerWorld::gen_chunks_internal(const std::string& uuid) {
|
||||||
// Logger::info("gen_chunks_internal");
|
// Logger::info("gen_chunks_internal");
|
||||||
m_chunk_gen_finished = false;
|
m_chunk_gen_finished = false;
|
||||||
|
|
||||||
ChunkPosSet required_chunks;
|
ChunkPosSet required_chunks;
|
||||||
compute_required_chunks(required_chunks, uuid);
|
compute_required_chunks(required_chunks, uuid);
|
||||||
|
|
||||||
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
|
|
||||||
|
|
||||||
std::vector<ChunkPos> need_gen_chunks_pos;
|
std::vector<ChunkPos> need_gen_chunks_pos;
|
||||||
|
|
||||||
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
|
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_player_mutex);
|
||||||
|
auto it = m_players.find(uuid);
|
||||||
|
if (it == m_players.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
it->second.update_chunk_set(required_chunks);
|
||||||
|
}
|
||||||
|
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
|
||||||
|
|
||||||
|
clear_unused_chunks();
|
||||||
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
|
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
|
||||||
|
|
||||||
if (need_gen_chunks_pos.empty()) {
|
if (need_gen_chunks_pos.empty() && m_new_chunks.empty()) {
|
||||||
m_could_gen = true;
|
m_could_gen = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
// Create new chunk
|
||||||
std::lock_guard lock(m_new_chunk_mutex);
|
std::lock_guard lock(m_new_chunk_mutex);
|
||||||
for (auto& pos : need_gen_chunks_pos) {
|
for (auto& pos : need_gen_chunks_pos) {
|
||||||
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
|
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
|
||||||
@@ -138,26 +259,24 @@ void ServerWorld::compute_required_chunks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerWorld::sync_and_collect_missing_chunks(
|
void ServerWorld::sync_and_collect_missing_chunks(
|
||||||
std::vector<ChunkPos>& need_gen_chunks_pos,
|
std::vector<ChunkPos>& need_gen_chunks_pos,
|
||||||
const ChunkPosSet& required_chunks) {
|
const ChunkPosSet& required_chunks) {
|
||||||
std::lock_guard lk(m_chunks_mutex);
|
{
|
||||||
for (auto it = m_chunks.begin(); it != m_chunks.end();) {
|
std::lock_guard lock(m_chunks_mutex);
|
||||||
if (required_chunks.find(it->first) == required_chunks.end()) {
|
for (auto pos : required_chunks) {
|
||||||
it = m_chunks.unsafe_erase(it);
|
auto it = m_chunks.find(pos);
|
||||||
} else {
|
if (it == m_chunks.end()) {
|
||||||
++it;
|
need_gen_chunks_pos.push_back(pos);
|
||||||
}
|
m_chunks.emplace(pos,
|
||||||
}
|
ChunkEntity{ChunkState::GENERATING, nullptr});
|
||||||
|
}
|
||||||
for (auto pos : required_chunks) {
|
|
||||||
auto it = m_chunks.find(pos);
|
|
||||||
if (it == m_chunks.end()) {
|
|
||||||
need_gen_chunks_pos.push_back(pos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
|
|
||||||
|
void ServerWorld::submit_new_chunks(const std::string& uuid) {
|
||||||
using enum ChunkLoadStyle;
|
using enum ChunkLoadStyle;
|
||||||
std::lock_guard lock(m_new_chunk_mutex);
|
std::lock_guard lock(m_new_chunk_mutex);
|
||||||
auto pool_ptr = m_gen_thread_pool.load();
|
auto pool_ptr = m_gen_thread_pool.load();
|
||||||
@@ -166,6 +285,7 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
|
|||||||
}
|
}
|
||||||
switch (m_chunk_load_style) {
|
switch (m_chunk_load_style) {
|
||||||
case RANDOM:
|
case RANDOM:
|
||||||
|
// Enqueue directly in random order
|
||||||
for (auto& [pos, task] : m_new_chunks) {
|
for (auto& [pos, task] : m_new_chunks) {
|
||||||
if (!task.future.valid()) {
|
if (!task.future.valid()) {
|
||||||
task.future =
|
task.future =
|
||||||
@@ -180,12 +300,8 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
|
|||||||
tasks.emplace_back(pos, &task);
|
tasks.emplace_back(pos, &task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glm::vec3 player_pos;
|
glm::vec3 player_pos = get_player_pos(uuid);
|
||||||
if (uuid == std::nullopt) {
|
|
||||||
player_pos = glm::vec3{0.0f};
|
|
||||||
} else {
|
|
||||||
player_pos = get_player_pos(uuid.value());
|
|
||||||
}
|
|
||||||
auto dist2 = [player_pos](ChunkPos chunk_pos) {
|
auto dist2 = [player_pos](ChunkPos chunk_pos) {
|
||||||
ChunkPos player_chunk_pos =
|
ChunkPos player_chunk_pos =
|
||||||
get_chunk_pos(player_pos.x, player_pos.z);
|
get_chunk_pos(player_pos.x, player_pos.z);
|
||||||
@@ -220,10 +336,17 @@ void ServerWorld::poll_finished_chunks() {
|
|||||||
if (pending.future.wait_for(0ms) != std::future_status::ready) {
|
if (pending.future.wait_for(0ms) != std::future_status::ready) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pending.future.get();
|
try {
|
||||||
|
pending.future.get();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Logger::error("Chunk generation failed: {}", e.what());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Spawn complete, move away
|
||||||
|
m_new_finished_chunk.emplace_back(
|
||||||
|
pair.first,
|
||||||
|
std::make_shared<ServerChunk>(std::move(pending.chunk)));
|
||||||
|
|
||||||
m_new_finished_chunk.emplace_back(pair.first,
|
|
||||||
std::move(pending.chunk));
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -246,7 +369,7 @@ void ServerWorld::start_gen_thread() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
m_need_gen_chunk = false;
|
m_need_gen_chunk = false;
|
||||||
std::optional<std::string> uuid{std::nullopt};
|
std::string uuid;
|
||||||
if (!m_need_gen_queue.empty()) {
|
if (!m_need_gen_queue.empty()) {
|
||||||
uuid = m_need_gen_queue.front();
|
uuid = m_need_gen_queue.front();
|
||||||
m_need_gen_queue.pop();
|
m_need_gen_queue.pop();
|
||||||
@@ -316,7 +439,7 @@ void ServerWorld::serever_run(std::stop_token stoken) {
|
|||||||
Logger::info("Server Thread Stopped!");
|
Logger::info("Server Thread Stopped!");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerWorld::need_gen(std::optional<std::string> uuid) {
|
void ServerWorld::need_gen(std::string uuid) {
|
||||||
|
|
||||||
// if (!m_could_gen) {
|
// if (!m_could_gen) {
|
||||||
// Logger::warn("It is generating or consuming new chunks");
|
// Logger::warn("It is generating or consuming new chunks");
|
||||||
@@ -325,9 +448,9 @@ void ServerWorld::need_gen(std::optional<std::string> uuid) {
|
|||||||
|
|
||||||
m_could_gen = false;
|
m_could_gen = false;
|
||||||
|
|
||||||
if (uuid) {
|
{
|
||||||
std::lock_guard lock(m_need_gen_queue_mutex);
|
std::lock_guard lock(m_need_gen_queue_mutex);
|
||||||
m_need_gen_queue.enqueue(*uuid);
|
m_need_gen_queue.enqueue(std::move(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_gen_player_pos = get_player("TestPlayer").get_player_pos();
|
// m_gen_player_pos = get_player("TestPlayer").get_player_pos();
|
||||||
@@ -359,7 +482,7 @@ bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
it->second.set_chunk_block(ServerChunk::index(x, y, z), id);
|
it->second.chunk->set_chunk_block(ServerChunk::index(x, y, z), id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +490,6 @@ void ServerWorld::hot_reload() {
|
|||||||
auto& config = Config::get();
|
auto& config = Config::get();
|
||||||
int dist = config.get<int>("world.rendering_distance");
|
int dist = config.get<int>("world.rendering_distance");
|
||||||
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
|
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
|
||||||
need_gen(std::nullopt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerWorld::rebuild_world() {
|
void ServerWorld::rebuild_world() {
|
||||||
@@ -391,7 +513,6 @@ void ServerWorld::rebuild_world() {
|
|||||||
ChunkGenerator::reload();
|
ChunkGenerator::reload();
|
||||||
start_thread_pool();
|
start_thread_pool();
|
||||||
start_gen_thread();
|
start_gen_thread();
|
||||||
need_gen(std::nullopt);
|
|
||||||
Arena arena;
|
Arena arena;
|
||||||
auto* rsp = Arena::Create<S2C_ClearAllChunks>(&arena);
|
auto* rsp = Arena::Create<S2C_ClearAllChunks>(&arena);
|
||||||
rsp->set_clear(true);
|
rsp->set_clear(true);
|
||||||
@@ -410,15 +531,27 @@ void ServerWorld::update() {
|
|||||||
{
|
{
|
||||||
std::lock_guard lk(m_chunks_mutex);
|
std::lock_guard lk(m_chunks_mutex);
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
auto size = m_new_finished_chunk.size();
|
||||||
|
if (size != 0) {
|
||||||
|
Logger::info("New generated {} chunks", size);
|
||||||
|
}
|
||||||
for (auto& x : m_new_finished_chunk) {
|
for (auto& x : m_new_finished_chunk) {
|
||||||
m_chunks.emplace(x.first, std::move(x.second));
|
auto it = m_chunks.find(x.pos);
|
||||||
|
if (it == m_chunks.end()) {
|
||||||
|
Logger::error(
|
||||||
|
"New Chunk {} {} not Find, don't move to m_chunks", x.pos.x,
|
||||||
|
x.pos.z);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it->second.chunk = std::move(x.chunk);
|
||||||
|
it->second.state = ChunkState::READY;
|
||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
if (consumed) {
|
if (consumed) {
|
||||||
m_could_gen = true;
|
m_could_gen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_time();
|
send_time();
|
||||||
for (auto& [id, timer] : m_timers) {
|
for (auto& [id, timer] : m_timers) {
|
||||||
timer.update();
|
timer.update();
|
||||||
@@ -469,14 +602,43 @@ void ServerWorld::handle_player_login(const std::string& name,
|
|||||||
std::shared_ptr<Session> session) {
|
std::shared_ptr<Session> session) {
|
||||||
std::string uuid = generate_uuid();
|
std::string uuid = generate_uuid();
|
||||||
Logger::info("Player {} (uuid {}) join the world", name, uuid);
|
Logger::info("Player {} (uuid {}) join the world", name, uuid);
|
||||||
|
bool sucess = true;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_player_mutex);
|
std::lock_guard lock(m_player_mutex);
|
||||||
m_players.emplace(
|
auto [_, inserted] = m_players.emplace(
|
||||||
std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
|
std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
|
||||||
std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
|
std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
|
||||||
|
if (!inserted) {
|
||||||
|
Logger::error("Player insert Fail");
|
||||||
|
}
|
||||||
|
sucess = inserted;
|
||||||
}
|
}
|
||||||
m_uuid_to_name.emplace(uuid, name);
|
|
||||||
Arena arena;
|
Arena arena;
|
||||||
|
if (!sucess) {
|
||||||
|
auto* rsp = Arena::Create<LoginRsp>(&arena);
|
||||||
|
rsp->set_success(false);
|
||||||
|
session->send(make_packet(*rsp));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_uuid_to_name.emplace(uuid, name);
|
||||||
|
// Pre-insert into new_chunks to ensure correct addition to waiting_player
|
||||||
|
/*ChunkPosSet required_chunks;
|
||||||
|
compute_required_chunks(required_chunks, uuid);
|
||||||
|
std::vector<ChunkPos> need_gen_chunks_pos;
|
||||||
|
|
||||||
|
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_new_chunk_mutex);
|
||||||
|
for (auto& pos : need_gen_chunks_pos) {
|
||||||
|
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
need_gen(uuid);
|
||||||
|
|
||||||
auto* rsp = Arena::Create<LoginRsp>(&arena);
|
auto* rsp = Arena::Create<LoginRsp>(&arena);
|
||||||
rsp->set_success(true);
|
rsp->set_success(true);
|
||||||
rsp->set_uuid(uuid);
|
rsp->set_uuid(uuid);
|
||||||
@@ -538,71 +700,8 @@ void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto pool = m_gen_thread_pool.load();
|
auto pool = m_gen_thread_pool.load();
|
||||||
pool->enqueue([task_id, uuid, pos, this]() {
|
pool->enqueue(
|
||||||
{
|
[task_id, uuid, pos, this]() { send_chunk(task_id, uuid, pos); });
|
||||||
std::shared_lock lock(m_player_mutex);
|
|
||||||
auto it = m_players.find(uuid);
|
|
||||||
if (it == m_players.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (task_id < it->second.task_id()) {
|
|
||||||
// Old chunk requests are simply discarded
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Arena arean;
|
|
||||||
ChunkDataRsp* rsp = Arena::Create<ChunkDataRsp>(&arean);
|
|
||||||
auto* rsq_pos = rsp->mutable_pos();
|
|
||||||
rsq_pos->set_x(pos.x);
|
|
||||||
rsq_pos->set_z(pos.z);
|
|
||||||
{
|
|
||||||
std::shared_lock lock(m_chunks_mutex);
|
|
||||||
auto it = m_chunks.find(pos);
|
|
||||||
if (it == m_chunks.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rsp->set_chunk_seed(it->second.seed());
|
|
||||||
rsp->set_biome_type(std::to_underlying(it->second.biome()));
|
|
||||||
auto* blocks = rsp->mutable_chunk_blocks();
|
|
||||||
auto& chunk_blocks = it->second.get_chunk_blocks();
|
|
||||||
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
|
|
||||||
auto& neighbor_blocks = it->second.get_neightbor_blocks();
|
|
||||||
auto assign =
|
|
||||||
[](auto* nb,
|
|
||||||
const std::optional<std::vector<BlockType>>& blocks) {
|
|
||||||
if (!blocks) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!nb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nb->Assign(blocks->begin(), blocks->end());
|
|
||||||
};
|
|
||||||
auto* nb1 = rsp->mutable_neighbor_blocks_1();
|
|
||||||
auto* nb2 = rsp->mutable_neighbor_blocks_2();
|
|
||||||
auto* nb3 = rsp->mutable_neighbor_blocks_3();
|
|
||||||
auto* nb4 = rsp->mutable_neighbor_blocks_4();
|
|
||||||
assign(nb1, neighbor_blocks[0]);
|
|
||||||
assign(nb2, neighbor_blocks[1]);
|
|
||||||
assign(nb3, neighbor_blocks[2]);
|
|
||||||
assign(nb4, neighbor_blocks[3]);
|
|
||||||
}
|
|
||||||
std::shared_ptr<Session> s;
|
|
||||||
{
|
|
||||||
std::shared_lock lock(m_player_mutex);
|
|
||||||
auto it = m_players.find(uuid);
|
|
||||||
if (it != m_players.end()) {
|
|
||||||
s = it->second.get_session();
|
|
||||||
it->second.update_sync_gametick(m_game_ticks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!s) {
|
|
||||||
Logger::error("Player {} session not exist", uuid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rsp->set_task_id(task_id);
|
|
||||||
s->send(make_packet(*rsp));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerWorld::handle_block_change(const BlockChangeReq& req) {
|
void ServerWorld::handle_block_change(const BlockChangeReq& req) {
|
||||||
@@ -683,4 +782,9 @@ void ServerWorld::set_chunk_load_style(int id) {
|
|||||||
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
|
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ServerWorld::chunk_size() const {
|
||||||
|
std::shared_lock lock(m_chunks_mutex);
|
||||||
|
return m_chunks.size();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
Reference in New Issue
Block a user