mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
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.
This commit is contained in:
@@ -3,9 +3,12 @@
|
|||||||
namespace Cubed {
|
namespace Cubed {
|
||||||
using EntityID = uint64_t;
|
using EntityID = uint64_t;
|
||||||
|
|
||||||
|
enum class EntityType { CREATURE, ITEM };
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
EntityID id;
|
EntityID id;
|
||||||
explicit Entity(EntityID id) : id(id) {}
|
EntityType type;
|
||||||
|
Entity(EntityID id, EntityType type) : id(id), type(type) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
@@ -12,15 +12,20 @@ class ServerWorld;
|
|||||||
class Session;
|
class Session;
|
||||||
class ServerEntityManager {
|
class ServerEntityManager {
|
||||||
public:
|
public:
|
||||||
|
static constexpr size_t PER_CREATURE_LIMITS = 100;
|
||||||
|
|
||||||
ServerEntityManager(ServerWorld& world);
|
ServerEntityManager(ServerWorld& world);
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void update();
|
void update();
|
||||||
// not thread safe
|
void add_creature(std::string_view name, const glm::vec3& world_pos);
|
||||||
void add_entity(std::string_view name, const glm::vec3& world_pos);
|
|
||||||
void destory(EntityID id);
|
void destory(EntityID id);
|
||||||
void handle_player_login(std::shared_ptr<Session> session);
|
void handle_player_login(std::shared_ptr<Session> session);
|
||||||
|
|
||||||
|
size_t max_creature_sum() const;
|
||||||
|
size_t creature_sum() const;
|
||||||
|
size_t entity_sum() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Command { CREATE, SEND_ALL_ENTITIES, DESTORY };
|
enum class Command { CREATE, SEND_ALL_ENTITIES, DESTORY };
|
||||||
struct EntityCreateElement {
|
struct EntityCreateElement {
|
||||||
@@ -43,6 +48,8 @@ private:
|
|||||||
std::variant<std::shared_ptr<Session>, EntityCreateElement, EntityID>;
|
std::variant<std::shared_ptr<Session>, EntityCreateElement, EntityID>;
|
||||||
using TaskPair = std::pair<Command, TaskElement>;
|
using TaskPair = std::pair<Command, TaskElement>;
|
||||||
ServerWorld& m_world;
|
ServerWorld& m_world;
|
||||||
|
std::atomic<size_t> m_creature_sum{0};
|
||||||
|
std::atomic<size_t> m_entity_sum{0};
|
||||||
tbb::concurrent_queue<TaskPair> m_tasks;
|
tbb::concurrent_queue<TaskPair> m_tasks;
|
||||||
entt::registry m_registry;
|
entt::registry m_registry;
|
||||||
EntityID m_next = 0;
|
EntityID m_next = 0;
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ public:
|
|||||||
uint32_t get_chunk_ref_count(const glm::vec3& pos) const;
|
uint32_t get_chunk_ref_count(const glm::vec3& pos) const;
|
||||||
ServerEntityManager& entity_manager();
|
ServerEntityManager& entity_manager();
|
||||||
std::shared_ptr<ThreadPool> get_compute_pool();
|
std::shared_ptr<ThreadPool> get_compute_pool();
|
||||||
|
size_t player_sum() const;
|
||||||
|
|
||||||
int get_block(const glm::ivec3& block_pos) const override;
|
int get_block(const glm::ivec3& block_pos) const override;
|
||||||
bool is_solid(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;
|
bool can_pass_block(const glm::ivec3& block_pos) const override;
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ public:
|
|||||||
|
|
||||||
bool is_recording() const;
|
bool is_recording() const;
|
||||||
|
|
||||||
|
RunMode runmode() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class PauseUI { PAUSE_MENU, INVENTORY };
|
enum class PauseUI { PAUSE_MENU, INVENTORY };
|
||||||
SceneManager& m_scene_manager;
|
SceneManager& m_scene_manager;
|
||||||
@@ -61,6 +63,7 @@ private:
|
|||||||
ErrorUI m_error_ui;
|
ErrorUI m_error_ui;
|
||||||
const Argument& m_argument;
|
const Argument& m_argument;
|
||||||
VoiceInputType m_input_type;
|
VoiceInputType m_input_type;
|
||||||
|
RunMode m_runmode = RunMode::HYBRID;
|
||||||
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
|
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
|
||||||
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
|
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
|
||||||
bool handle_window_resize_event(const WindowResizeEvent& e) override;
|
bool handle_window_resize_event(const WindowResizeEvent& e) override;
|
||||||
|
|||||||
@@ -343,17 +343,20 @@ void DevPanel::show_world_tab_item() {
|
|||||||
if (ImGui::BeginTabItem("world")) {
|
if (ImGui::BeginTabItem("world")) {
|
||||||
|
|
||||||
if (ImGui::BeginTabBar("World Kind")) {
|
if (ImGui::BeginTabBar("World Kind")) {
|
||||||
auto& param = m_world_scene.scene_manager().world_scene_param();
|
auto mode = m_world_scene.runmode();
|
||||||
if (param.host_game) {
|
if (mode == RunMode::HYBRID || mode == RunMode::SERVER_ONLY) {
|
||||||
if (ImGui::BeginTabItem("ServerWorld")) {
|
if (ImGui::BeginTabItem("ServerWorld")) {
|
||||||
show_server_world_table_bar();
|
show_server_world_table_bar();
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mode == RunMode::HYBRID || mode == RunMode::CLIENT_ONLY) {
|
||||||
if (ImGui::BeginTabItem("Client World")) {
|
if (ImGui::BeginTabItem("Client World")) {
|
||||||
show_client_world_table_bar();
|
show_client_world_table_bar();
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndTabBar();
|
ImGui::EndTabBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,47 +369,49 @@ void DevPanel::show_world_tab_item() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DevPanel::show_server_world_table_bar() {
|
void DevPanel::show_server_world_table_bar() {
|
||||||
|
auto& world = m_world_scene.server_world();
|
||||||
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
|
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
|
||||||
|
|
||||||
ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d",
|
ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d",
|
||||||
m_world_scene.server_world().gen_pool_threads(),
|
world.gen_pool_threads(), world.max_threads(),
|
||||||
m_world_scene.server_world().max_threads(), RESERVED_THREADS);
|
RESERVED_THREADS);
|
||||||
ImGui::SliderInt("Set Pool Threads", &m_threads, 1,
|
ImGui::SliderInt("Set Pool Threads", &m_threads, 1, world.max_threads());
|
||||||
m_world_scene.server_world().max_threads());
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Set")) {
|
if (ImGui::Button("Set")) {
|
||||||
m_world_scene.server_world().change_pool_threads(
|
world.change_pool_threads(ServerWorld::ThreadPoolKind::GEN, m_threads);
|
||||||
ServerWorld::ThreadPoolKind::GEN, m_threads);
|
|
||||||
}
|
}
|
||||||
if (m_threads >
|
if (m_threads > world.max_threads() - RESERVED_THREADS) {
|
||||||
m_world_scene.server_world().max_threads() - RESERVED_THREADS) {
|
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(1.0f, 1.0f, 0.0f, 1.0f),
|
ImVec4(1.0f, 1.0f, 0.0f, 1.0f),
|
||||||
"Waring: When the threads in the thread pool exceed \n(maximum "
|
"Waring: When the threads in the thread pool exceed \n(maximum "
|
||||||
"threads minus reserved threads), \nit may cause stuttering.");
|
"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,
|
if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, CHUNK_LOAD_STYLE,
|
||||||
IM_ARRAYSIZE(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")) {
|
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();
|
ImGui::SameLine();
|
||||||
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
|
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
|
||||||
if (m_gen_thread_running) {
|
if (m_gen_thread_running) {
|
||||||
m_world_scene.server_world().start_gen_thread();
|
world.start_gen_thread();
|
||||||
} else {
|
} else {
|
||||||
m_world_scene.server_world().stop_gen_thread();
|
world.stop_gen_thread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::Text("Server Chunk Size %d",
|
ImGui::Text("Server Chunk Size %d", world.chunk_size());
|
||||||
m_world_scene.server_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::BeginTabBar("World Settings")) {
|
||||||
if (ImGui::BeginTabItem("Time")) {
|
if (ImGui::BeginTabItem("Time")) {
|
||||||
show_time_table_bar();
|
show_time_table_bar();
|
||||||
|
|||||||
@@ -829,7 +829,7 @@ void ChunkGenerator::spawn_creature() {
|
|||||||
}
|
}
|
||||||
auto [world_x, world_y, world_z] = Chunk::block_to_world(
|
auto [world_x, world_y, world_z] = Chunk::block_to_world(
|
||||||
x, y + 1, z, chunk_pos.x, chunk_pos.z);
|
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,
|
SpawnDefaults::PIG.name,
|
||||||
glm::vec3{world_x, world_y, world_z});
|
glm::vec3{world_x, world_y, world_z});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ void ClientEntityManager::init() {
|
|||||||
m_factories.emplace("cubed:pig", [this](EntityID id) {
|
m_factories.emplace("cubed:pig", [this](EntityID id) {
|
||||||
BaseClientCreature c;
|
BaseClientCreature c;
|
||||||
c.model = ModelManager::instance().get_model_id("cubed:pig");
|
c.model = ModelManager::instance().get_model_id("cubed:pig");
|
||||||
create_entity_in_registry(id, Entity{id}, EntityInfo{"cubed:pig", ""},
|
create_entity_in_registry(id, Entity{id, EntityType::CREATURE},
|
||||||
std::move(c), PigTag{}, RenderTransform{});
|
EntityInfo{"cubed:pig", ""}, std::move(c),
|
||||||
|
PigTag{}, RenderTransform{});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// not thread safe
|
// not thread safe
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ void ServerEntityManager::init() {
|
|||||||
c.movement.deceleration = PigDefaults::DECELERATION;
|
c.movement.deceleration = PigDefaults::DECELERATION;
|
||||||
c.velocity.max.x = c.velocity.max.z = PigDefaults::MAX_SPEED;
|
c.velocity.max.x = c.velocity.max.z = PigDefaults::MAX_SPEED;
|
||||||
return create_entity_in_factory(
|
return create_entity_in_factory(
|
||||||
Entity{m_next}, EntityInfo{"cubed:pig", ""}, std::move(c), PigTag{},
|
Entity{m_next, EntityType::CREATURE}, EntityInfo{"cubed:pig", ""},
|
||||||
AIBase{}, WanderAITag{}, MoveBoost{});
|
std::move(c), PigTag{}, AIBase{}, WanderAITag{}, MoveBoost{});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void ServerEntityManager::update() {
|
void ServerEntityManager::update() {
|
||||||
@@ -134,8 +134,13 @@ void ServerEntityManager::handle_task() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerEntityManager::add_entity(std::string_view name,
|
void ServerEntityManager::add_creature(std::string_view name,
|
||||||
const glm::vec3& world_pos) {
|
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,
|
m_tasks.emplace(Command::CREATE,
|
||||||
EntityCreateElement{std::string(name), world_pos});
|
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));
|
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,
|
void ServerEntityManager::create_entity(std::string_view name,
|
||||||
const glm::vec3& pos) {
|
const glm::vec3& pos) {
|
||||||
ASSERT(m_factories.contains(name));
|
ASSERT(m_factories.contains(name));
|
||||||
@@ -159,6 +173,7 @@ void ServerEntityManager::create_entity(std::string_view name,
|
|||||||
ASSERT(t);
|
ASSERT(t);
|
||||||
t->transform.position.value = pos;
|
t->transform.position.value = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_entity_create(e, name, 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) {
|
void ServerEntityManager::handle_entity_destory(EntityID id) {
|
||||||
|
if (m_entity_sum == 0) {
|
||||||
|
Logger::error("entity sum is 0!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
acc a;
|
acc a;
|
||||||
if (!m_entities.find(a, id)) {
|
if (!m_entities.find(a, id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto e = m_registry.try_get<Entity>(a->second);
|
||||||
|
ASSERT(e);
|
||||||
|
if (e->type == EntityType::CREATURE) {
|
||||||
|
--m_creature_sum;
|
||||||
|
}
|
||||||
m_registry.destroy(a->second);
|
m_registry.destroy(a->second);
|
||||||
m_entities.erase(a);
|
m_entities.erase(a);
|
||||||
|
|
||||||
|
--m_entity_sum;
|
||||||
auto sessions = m_world.get_all_session();
|
auto sessions = m_world.get_all_session();
|
||||||
Arena arena;
|
Arena arena;
|
||||||
auto* s2c = Arena::Create<S2CEntityDestory>(&arena);
|
auto* s2c = Arena::Create<S2CEntityDestory>(&arena);
|
||||||
|
|||||||
@@ -896,7 +896,7 @@ void ServerWorld::handle_block_change(const BlockChangeReq& req) {
|
|||||||
|
|
||||||
void ServerWorld::handle_entity_create(C2SEntityCreateRequest& 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) {
|
void ServerWorld::handle_entity_destory(C2SEntityDestoryRequest& req) {
|
||||||
m_entity_manager.destory(req.id());
|
m_entity_manager.destory(req.id());
|
||||||
@@ -1133,4 +1133,7 @@ ServerEntityManager& ServerWorld::entity_manager() { return m_entity_manager; }
|
|||||||
std::shared_ptr<ThreadPool> ServerWorld::get_compute_pool() {
|
std::shared_ptr<ThreadPool> ServerWorld::get_compute_pool() {
|
||||||
return m_compute_thread_pool.load();
|
return m_compute_thread_pool.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t ServerWorld::player_sum() const { return m_player_sum.load(); }
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
@@ -129,21 +129,20 @@ void WorldScene::on_enter() {
|
|||||||
load_config();
|
load_config();
|
||||||
m_error_ui.init();
|
m_error_ui.init();
|
||||||
m_client = std::make_shared<NetworkClient>(m_client_world);
|
m_client = std::make_shared<NetworkClient>(m_client_world);
|
||||||
RunMode mode = RunMode::HYBRID;
|
|
||||||
if (m_argument.direct_enter) {
|
if (m_argument.direct_enter) {
|
||||||
if (m_argument.ip) {
|
if (m_argument.ip) {
|
||||||
mode = RunMode::CLIENT_ONLY;
|
m_runmode = RunMode::CLIENT_ONLY;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!m_scene_manager.world_scene_param().host_game) {
|
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.direct_enter) {
|
||||||
if (!m_argument.ip) {
|
if (!m_argument.ip) {
|
||||||
ChunkGenerator::init();
|
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);
|
m_client->start("127.0.0.1", *m_argument.port);
|
||||||
} else {
|
} else {
|
||||||
m_client->start(*m_argument.ip, *m_argument.port);
|
m_client->start(*m_argument.ip, *m_argument.port);
|
||||||
@@ -159,7 +158,7 @@ void WorldScene::on_enter() {
|
|||||||
} else {
|
} else {
|
||||||
ChunkGenerator::init();
|
ChunkGenerator::init();
|
||||||
}
|
}
|
||||||
m_server.start_server(param.port, mode);
|
m_server.start_server(param.port, m_runmode);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_client->start(param.ip, param.port);
|
m_client->start(param.ip, param.port);
|
||||||
@@ -169,7 +168,7 @@ void WorldScene::on_enter() {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
m_client_world.init(m_argument.player.value_or("Unknown"), m_client,
|
m_client_world.init(m_argument.player.value_or("Unknown"), m_client,
|
||||||
mode);
|
m_runmode);
|
||||||
|
|
||||||
Logger::info("World Init Success");
|
Logger::info("World Init Success");
|
||||||
m_camera.camera_init(&m_client_world.get_player());
|
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 {
|
bool WorldScene::is_recording() const {
|
||||||
return m_client_world.get_audio().audio_recording().is_recording();
|
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) {
|
void WorldScene::set_error(std::string_view error) {
|
||||||
Logger::error("WorldScene Error Set {}", error);
|
Logger::error("WorldScene Error Set {}", error);
|
||||||
m_error_ui.set_error(error);
|
m_error_ui.set_error(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user