From 94d92f2159ebeb6809d90cc09e733a27d55afdcb Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Thu, 6 Aug 2026 20:13:55 +0800 Subject: [PATCH] feat(ecs): add entity type separation and creature limits Replace generic entity creation with typed creatures and items. Rename `add_entity` to `add_creature`, enforce per-player creature cap, and track entity/creature totals for metrics. Expose run mode to dev panel and show new counts. --- include/Cubed/gameplay/ecs/entity.hpp | 5 +- .../Cubed/gameplay/server_entity_manager.hpp | 11 ++++- include/Cubed/gameplay/server_world.hpp | 2 + include/Cubed/scene/world_scene.hpp | 3 ++ src/dev_panel.cpp | 47 ++++++++++--------- src/gameplay/chunk_generator.cpp | 2 +- src/gameplay/client_entity_manager.cpp | 5 +- src/gameplay/server_entity_manager.cpp | 34 ++++++++++++-- src/gameplay/server_world.cpp | 5 +- src/scene/world_scene.cpp | 14 +++--- 10 files changed, 90 insertions(+), 38 deletions(-) diff --git a/include/Cubed/gameplay/ecs/entity.hpp b/include/Cubed/gameplay/ecs/entity.hpp index d959538..67699c4 100644 --- a/include/Cubed/gameplay/ecs/entity.hpp +++ b/include/Cubed/gameplay/ecs/entity.hpp @@ -3,9 +3,12 @@ namespace Cubed { using EntityID = uint64_t; +enum class EntityType { CREATURE, ITEM }; + struct Entity { EntityID id; - explicit Entity(EntityID id) : id(id) {} + EntityType type; + Entity(EntityID id, EntityType type) : id(id), type(type) {} }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/server_entity_manager.hpp b/include/Cubed/gameplay/server_entity_manager.hpp index 2830cca..e5af786 100644 --- a/include/Cubed/gameplay/server_entity_manager.hpp +++ b/include/Cubed/gameplay/server_entity_manager.hpp @@ -12,15 +12,20 @@ class ServerWorld; class Session; class ServerEntityManager { public: + static constexpr size_t PER_CREATURE_LIMITS = 100; + ServerEntityManager(ServerWorld& world); void init(); void update(); - // not thread safe - void add_entity(std::string_view name, const glm::vec3& world_pos); + void add_creature(std::string_view name, const glm::vec3& world_pos); void destory(EntityID id); void handle_player_login(std::shared_ptr session); + size_t max_creature_sum() const; + size_t creature_sum() const; + size_t entity_sum() const; + private: enum class Command { CREATE, SEND_ALL_ENTITIES, DESTORY }; struct EntityCreateElement { @@ -43,6 +48,8 @@ private: std::variant, EntityCreateElement, EntityID>; using TaskPair = std::pair; ServerWorld& m_world; + std::atomic m_creature_sum{0}; + std::atomic m_entity_sum{0}; tbb::concurrent_queue m_tasks; entt::registry m_registry; EntityID m_next = 0; diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 5fa12c2..e1e6930 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -98,6 +98,8 @@ public: uint32_t get_chunk_ref_count(const glm::vec3& pos) const; ServerEntityManager& entity_manager(); std::shared_ptr get_compute_pool(); + size_t player_sum() const; + int get_block(const glm::ivec3& block_pos) const override; bool is_solid(const glm::ivec3& block_pos) const override; bool can_pass_block(const glm::ivec3& block_pos) const override; diff --git a/include/Cubed/scene/world_scene.hpp b/include/Cubed/scene/world_scene.hpp index 6878d4b..448142e 100644 --- a/include/Cubed/scene/world_scene.hpp +++ b/include/Cubed/scene/world_scene.hpp @@ -42,6 +42,8 @@ public: bool is_recording() const; + RunMode runmode() const; + private: enum class PauseUI { PAUSE_MENU, INVENTORY }; SceneManager& m_scene_manager; @@ -61,6 +63,7 @@ private: ErrorUI m_error_ui; const Argument& m_argument; VoiceInputType m_input_type; + RunMode m_runmode = RunMode::HYBRID; bool handle_mouse_move_event(const MouseMoveEvent& e) override; bool handle_mouse_button_event(const MouseButtonEvent& e) override; bool handle_window_resize_event(const WindowResizeEvent& e) override; diff --git a/src/dev_panel.cpp b/src/dev_panel.cpp index ef633c2..354020d 100644 --- a/src/dev_panel.cpp +++ b/src/dev_panel.cpp @@ -343,17 +343,20 @@ void DevPanel::show_world_tab_item() { if (ImGui::BeginTabItem("world")) { if (ImGui::BeginTabBar("World Kind")) { - auto& param = m_world_scene.scene_manager().world_scene_param(); - if (param.host_game) { + auto mode = m_world_scene.runmode(); + if (mode == RunMode::HYBRID || mode == RunMode::SERVER_ONLY) { if (ImGui::BeginTabItem("ServerWorld")) { show_server_world_table_bar(); ImGui::EndTabItem(); } } - if (ImGui::BeginTabItem("Client World")) { - show_client_world_table_bar(); - ImGui::EndTabItem(); + if (mode == RunMode::HYBRID || mode == RunMode::CLIENT_ONLY) { + if (ImGui::BeginTabItem("Client World")) { + show_client_world_table_bar(); + ImGui::EndTabItem(); + } } + ImGui::EndTabBar(); } @@ -366,47 +369,49 @@ void DevPanel::show_world_tab_item() { } void DevPanel::show_server_world_table_bar() { - + auto& world = m_world_scene.server_world(); ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed()); ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d", - m_world_scene.server_world().gen_pool_threads(), - m_world_scene.server_world().max_threads(), RESERVED_THREADS); - ImGui::SliderInt("Set Pool Threads", &m_threads, 1, - m_world_scene.server_world().max_threads()); + world.gen_pool_threads(), world.max_threads(), + RESERVED_THREADS); + ImGui::SliderInt("Set Pool Threads", &m_threads, 1, world.max_threads()); ImGui::SameLine(); if (ImGui::Button("Set")) { - m_world_scene.server_world().change_pool_threads( - ServerWorld::ThreadPoolKind::GEN, m_threads); + world.change_pool_threads(ServerWorld::ThreadPoolKind::GEN, m_threads); } - if (m_threads > - m_world_scene.server_world().max_threads() - RESERVED_THREADS) { + if (m_threads > world.max_threads() - RESERVED_THREADS) { ImGui::TextColored( ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Waring: When the threads in the thread pool exceed \n(maximum " "threads minus reserved threads), \nit may cause stuttering."); } - m_chunk_style = m_world_scene.server_world().chunk_load_style(); + m_chunk_style = world.chunk_load_style(); if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, CHUNK_LOAD_STYLE, IM_ARRAYSIZE(CHUNK_LOAD_STYLE))) { - m_world_scene.server_world().set_chunk_load_style(m_chunk_style); + world.set_chunk_load_style(m_chunk_style); } if (ImGui::Button("Request Chunk Build")) { - m_world_scene.server_world().need_gen(m_player->get_uuid()); + world.need_gen(m_player->get_uuid()); } ImGui::SameLine(); if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) { if (m_gen_thread_running) { - m_world_scene.server_world().start_gen_thread(); + world.start_gen_thread(); } else { - m_world_scene.server_world().stop_gen_thread(); + world.stop_gen_thread(); } } - ImGui::Text("Server Chunk Size %d", - m_world_scene.server_world().chunk_size()); + ImGui::Text("Server Chunk Size %d", world.chunk_size()); + ImGui::SameLine(); + ImGui::Text("Server Player Sum %zu", world.player_sum()); + ImGui::Text("Server Entity Sum %zu", world.entity_manager().entity_sum()); + ImGui::SameLine(); + ImGui::Text("Server Creature Sum %zu", + world.entity_manager().creature_sum()); if (ImGui::BeginTabBar("World Settings")) { if (ImGui::BeginTabItem("Time")) { show_time_table_bar(); diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index b10bdf1..aa09c40 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -829,7 +829,7 @@ void ChunkGenerator::spawn_creature() { } auto [world_x, world_y, world_z] = Chunk::block_to_world( x, y + 1, z, chunk_pos.x, chunk_pos.z); - m_chunk.world().entity_manager().add_entity( + m_chunk.world().entity_manager().add_creature( SpawnDefaults::PIG.name, glm::vec3{world_x, world_y, world_z}); } diff --git a/src/gameplay/client_entity_manager.cpp b/src/gameplay/client_entity_manager.cpp index 356bf0f..b8c7d11 100644 --- a/src/gameplay/client_entity_manager.cpp +++ b/src/gameplay/client_entity_manager.cpp @@ -31,8 +31,9 @@ void ClientEntityManager::init() { m_factories.emplace("cubed:pig", [this](EntityID id) { BaseClientCreature c; c.model = ModelManager::instance().get_model_id("cubed:pig"); - create_entity_in_registry(id, Entity{id}, EntityInfo{"cubed:pig", ""}, - std::move(c), PigTag{}, RenderTransform{}); + create_entity_in_registry(id, Entity{id, EntityType::CREATURE}, + EntityInfo{"cubed:pig", ""}, std::move(c), + PigTag{}, RenderTransform{}); }); } // not thread safe diff --git a/src/gameplay/server_entity_manager.cpp b/src/gameplay/server_entity_manager.cpp index 4b70cf8..47bbe0e 100644 --- a/src/gameplay/server_entity_manager.cpp +++ b/src/gameplay/server_entity_manager.cpp @@ -27,8 +27,8 @@ void ServerEntityManager::init() { c.movement.deceleration = PigDefaults::DECELERATION; c.velocity.max.x = c.velocity.max.z = PigDefaults::MAX_SPEED; return create_entity_in_factory( - Entity{m_next}, EntityInfo{"cubed:pig", ""}, std::move(c), PigTag{}, - AIBase{}, WanderAITag{}, MoveBoost{}); + Entity{m_next, EntityType::CREATURE}, EntityInfo{"cubed:pig", ""}, + std::move(c), PigTag{}, AIBase{}, WanderAITag{}, MoveBoost{}); }); } void ServerEntityManager::update() { @@ -134,8 +134,13 @@ void ServerEntityManager::handle_task() { } } -void ServerEntityManager::add_entity(std::string_view name, - const glm::vec3& world_pos) { +void ServerEntityManager::add_creature(std::string_view name, + const glm::vec3& world_pos) { + if (m_creature_sum.fetch_add(1) >= max_creature_sum()) { + m_creature_sum.fetch_sub(1); + return; + } + ++m_entity_sum; m_tasks.emplace(Command::CREATE, EntityCreateElement{std::string(name), world_pos}); } @@ -149,6 +154,15 @@ void ServerEntityManager::handle_player_login( m_tasks.emplace(Command::SEND_ALL_ENTITIES, std::move(session)); } +size_t ServerEntityManager::max_creature_sum() const { + return PER_CREATURE_LIMITS * m_world.player_sum(); +} + +size_t ServerEntityManager::creature_sum() const { + return m_creature_sum.load(); +} +size_t ServerEntityManager::entity_sum() const { return m_entity_sum.load(); } + void ServerEntityManager::create_entity(std::string_view name, const glm::vec3& pos) { ASSERT(m_factories.contains(name)); @@ -159,6 +173,7 @@ void ServerEntityManager::create_entity(std::string_view name, ASSERT(t); t->transform.position.value = pos; } + handle_entity_create(e, name, pos); } @@ -193,12 +208,23 @@ void ServerEntityManager::handle_entity_create(EntityID id, } void ServerEntityManager::handle_entity_destory(EntityID id) { + if (m_entity_sum == 0) { + Logger::error("entity sum is 0!"); + return; + } acc a; if (!m_entities.find(a, id)) { return; } + auto e = m_registry.try_get(a->second); + ASSERT(e); + if (e->type == EntityType::CREATURE) { + --m_creature_sum; + } m_registry.destroy(a->second); m_entities.erase(a); + + --m_entity_sum; auto sessions = m_world.get_all_session(); Arena arena; auto* s2c = Arena::Create(&arena); diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index b1340a1..5fae431 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -896,7 +896,7 @@ void ServerWorld::handle_block_change(const BlockChangeReq& req) { void ServerWorld::handle_entity_create(C2SEntityCreateRequest& req) { - m_entity_manager.add_entity(req.name(), Tools::get_net_vec3(req.pos())); + m_entity_manager.add_creature(req.name(), Tools::get_net_vec3(req.pos())); } void ServerWorld::handle_entity_destory(C2SEntityDestoryRequest& req) { m_entity_manager.destory(req.id()); @@ -1133,4 +1133,7 @@ ServerEntityManager& ServerWorld::entity_manager() { return m_entity_manager; } std::shared_ptr ServerWorld::get_compute_pool() { return m_compute_thread_pool.load(); } + +size_t ServerWorld::player_sum() const { return m_player_sum.load(); } + } // namespace Cubed \ No newline at end of file diff --git a/src/scene/world_scene.cpp b/src/scene/world_scene.cpp index 6ba1c82..6e5b0e1 100644 --- a/src/scene/world_scene.cpp +++ b/src/scene/world_scene.cpp @@ -129,21 +129,20 @@ void WorldScene::on_enter() { load_config(); m_error_ui.init(); m_client = std::make_shared(m_client_world); - RunMode mode = RunMode::HYBRID; if (m_argument.direct_enter) { if (m_argument.ip) { - mode = RunMode::CLIENT_ONLY; + m_runmode = RunMode::CLIENT_ONLY; } } else { if (!m_scene_manager.world_scene_param().host_game) { - mode = RunMode::CLIENT_ONLY; + m_runmode = RunMode::CLIENT_ONLY; } } if (m_argument.direct_enter) { if (!m_argument.ip) { ChunkGenerator::init(); - m_server.start_server(*m_argument.port, mode); + m_server.start_server(*m_argument.port, m_runmode); m_client->start("127.0.0.1", *m_argument.port); } else { m_client->start(*m_argument.ip, *m_argument.port); @@ -159,7 +158,7 @@ void WorldScene::on_enter() { } else { ChunkGenerator::init(); } - m_server.start_server(param.port, mode); + m_server.start_server(param.port, m_runmode); } m_client->start(param.ip, param.port); @@ -169,7 +168,7 @@ void WorldScene::on_enter() { try { m_client_world.init(m_argument.player.value_or("Unknown"), m_client, - mode); + m_runmode); Logger::info("World Init Success"); m_camera.camera_init(&m_client_world.get_player()); @@ -461,6 +460,9 @@ void WorldScene::handle_chat_message(ChatMessage& message) { bool WorldScene::is_recording() const { return m_client_world.get_audio().audio_recording().is_recording(); } + +RunMode WorldScene::runmode() const { return m_runmode; } + void WorldScene::set_error(std::string_view error) { Logger::error("WorldScene Error Set {}", error); m_error_ui.set_error(error);