feat(gameplay): implement entity system with concurrent task handling and fix model loading

This commit is contained in:
2026-07-30 15:05:56 +08:00
parent efa49a98b7
commit b166bab4f3
10 changed files with 109 additions and 30 deletions

View File

@@ -20,6 +20,8 @@ public:
void receive_entity_create(S2CEntityCreate& s2c); void receive_entity_create(S2CEntityCreate& s2c);
const entt::registry& get_registry() const;
private: private:
struct EntityCreateElement { struct EntityCreateElement {
EntityID id; EntityID id;
@@ -41,7 +43,14 @@ private:
void handle_task(); void handle_task();
template <typename... Args> void add_entity(EntityID id, Args&&... args) { template <typename... Args>
void create_entity_in_registry(EntityID id, Args&&... args) {
{
cacc a;
if (m_entities.find(a, id)) {
return;
}
}
auto entity = m_registry.create(); auto entity = m_registry.create();
((m_registry.emplace<std::remove_cvref_t<Args>>( ((m_registry.emplace<std::remove_cvref_t<Args>>(

View File

@@ -4,30 +4,45 @@
#include <entt/entt.hpp> #include <entt/entt.hpp>
#include <tbb/concurrent_hash_map.h> #include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
namespace Cubed { namespace Cubed {
class ServerWorld; class ServerWorld;
class Session;
class ServerEntityManager { class ServerEntityManager {
public: public:
ServerEntityManager(ServerWorld& world); ServerEntityManager(ServerWorld& world);
void init(); void init();
void update();
// not thread safe // not thread safe
void add_entity(std::string_view name, const glm::vec3& pos); void add_entity(std::string_view name, const glm::vec3& pos);
void handle_player_login(std::shared_ptr<Session> session);
private: private:
enum class Command { CREATE, SEND_ALL_ENTITIES };
struct EntityCreateElement {
std::string name;
glm::vec3 pos;
};
using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>; using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>;
using acc = EntityMap::accessor; using acc = EntityMap::accessor;
using cacc = EntityMap::const_accessor; using cacc = EntityMap::const_accessor;
using CreateFunc = std::function<EntityID()>; using CreateFunc = std::function<EntityID()>;
using TaskElement =
std::variant<std::shared_ptr<Session>, EntityCreateElement>;
using TaskPair = std::pair<Command, TaskElement>;
ServerWorld& m_world; ServerWorld& m_world;
tbb::concurrent_queue<TaskPair> m_tasks;
entt::registry m_registry; entt::registry m_registry;
EntityID m_next = 0; EntityID m_next = 0;
EntityMap m_entities; EntityMap m_entities;
std::unordered_map<std::string_view, CreateFunc> m_factories; std::unordered_map<std::string_view, CreateFunc> m_factories;
void create_entity(std::string_view name, const glm::vec3& pos);
void send_entity_create(EntityID id, std::string_view name, void send_entity_create(EntityID id, std::string_view name,
const glm::vec3& pos); const glm::vec3& pos);
void handle_task();
void send_all_entities(std::shared_ptr<Session>& session);
template <typename... Args> EntityID add_entity(Args&&... args) { template <typename... Args> EntityID add_entity(Args&&... args) {
auto entity = m_registry.create(); auto entity = m_registry.create();

View File

@@ -1,5 +1,6 @@
#include "Cubed/gameplay/client_entity_manager.hpp" #include "Cubed/gameplay/client_entity_manager.hpp"
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/gameplay/creatures/pig.hpp" #include "Cubed/gameplay/creatures/pig.hpp"
#include "Cubed/gameplay/ecs/client_entity.hpp" #include "Cubed/gameplay/ecs/client_entity.hpp"
#include "Cubed/gameplay/ecs/identity.hpp" #include "Cubed/gameplay/ecs/identity.hpp"
@@ -9,24 +10,25 @@
namespace Cubed { namespace Cubed {
ClientEntityManager::ClientEntityManager(ClientWorld& world) : m_world(world) {} ClientEntityManager::ClientEntityManager(ClientWorld& world) : m_world(world) {}
void ClientEntityManager::update() { handle_task(); } void ClientEntityManager::update() { handle_task(); }
void ClientEntityManager::init() { 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");
add_entity(id, Entity{id}, EntityInfo{"cubed:pig", ""}, std::move(c), create_entity_in_registry(id, Entity{id}, EntityInfo{"cubed:pig", ""},
PigTag{}); std::move(c), PigTag{});
}); });
} }
// not thread safe // not thread safe
void ClientEntityManager::add_entity(EntityID id, std::string_view name, void ClientEntityManager::add_entity(EntityID id, std::string_view name,
const glm::vec3& pos) { const glm::vec3& pos) {
ASSERT(m_factories.contains(name));
m_factories[name](id); m_factories[name](id);
acc a; acc a;
ASSERT(m_entities.find(a, id)); ASSERT(m_entities.find(a, id));
auto* c = m_registry.try_get<Transform>(a->second); auto* c = m_registry.try_get<BaseClientCreature>(a->second);
ASSERT(c); ASSERT(c);
c->position.value = pos; c->transform.position.value = pos;
} }
void ClientEntityManager::receive_entity_create(S2CEntityCreate& s2c) { void ClientEntityManager::receive_entity_create(S2CEntityCreate& s2c) {
@@ -44,10 +46,13 @@ void ClientEntityManager::handle_task() {
case Command::CREATE: case Command::CREATE:
auto* p = std::get_if<EntityCreateElement>(&pair.second); auto* p = std::get_if<EntityCreateElement>(&pair.second);
ASSERT(p); ASSERT(p);
add_entity(p->id, p->name, p->pos); add_entity(p->id, p->name, p->pos);
break;
} }
} }
} }
const entt::registry& ClientEntityManager::get_registry() const {
return m_registry;
}
} // namespace Cubed } // namespace Cubed

View File

@@ -731,6 +731,7 @@ void ClientWorld::send_chat_message(ChatMessage& message) {
void ClientWorld::update(float delta_time) { void ClientWorld::update(float delta_time) {
m_player_manager.update(delta_time); m_player_manager.update(delta_time);
m_entity_manager.update();
{ {
std::lock_guard lk(m_delete_vbo_mutex); std::lock_guard lk(m_delete_vbo_mutex);
m_pending_delete_vbo.clear(); m_pending_delete_vbo.clear();

View File

@@ -93,7 +93,7 @@ asio::awaitable<void> NetworkClient::read_loop() {
} break; } break;
case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): { case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): {
auto* rsp = Arena::Create<BlockChangeRsp>(&arena); auto* rsp = Arena::Create<BlockChangeRsp>(&arena);
Logger::info("Client: Receive Block Change rsp");
if (decode_packet(*rsp, body_data, header)) { if (decode_packet(*rsp, body_data, header)) {
m_world.receive_block_change(*rsp); m_world.receive_block_change(*rsp);
} }
@@ -148,6 +148,7 @@ asio::awaitable<void> NetworkClient::read_loop() {
if (decode_packet(*msg, body_data, header)) { if (decode_packet(*msg, body_data, header)) {
m_world.entity_manager().receive_entity_create(*msg); m_world.entity_manager().receive_entity_create(*msg);
} }
} break; } break;
} }
} }

View File

@@ -21,9 +21,39 @@ void ServerEntityManager::init() {
std::move(c), PigTag{}); std::move(c), PigTag{});
}); });
} }
void ServerEntityManager::update() { handle_task(); }
void ServerEntityManager::handle_task() {
TaskPair pair;
while (m_tasks.try_pop(pair)) {
switch (pair.first) {
case Command::CREATE: {
auto* c = std::get_if<EntityCreateElement>(&pair.second);
ASSERT(c);
create_entity(c->name, c->pos);
} break;
case Command::SEND_ALL_ENTITIES: {
auto* c = std::get_if<std::shared_ptr<Session>>(&pair.second);
ASSERT(c);
send_all_entities(*c);
} break;
}
}
}
void ServerEntityManager::add_entity(std::string_view name, void ServerEntityManager::add_entity(std::string_view name,
const glm::vec3& pos) { const glm::vec3& pos) {
m_tasks.emplace(Command::CREATE,
EntityCreateElement{std::string(name), pos});
}
void ServerEntityManager::handle_player_login(
std::shared_ptr<Session> session) {
m_tasks.emplace(Command::SEND_ALL_ENTITIES, std::move(session));
}
void ServerEntityManager::create_entity(std::string_view name,
const glm::vec3& pos) {
ASSERT(m_factories.contains(name)); ASSERT(m_factories.contains(name));
auto e = m_factories[name](); auto e = m_factories[name]();
acc c; acc c;
@@ -35,6 +65,20 @@ void ServerEntityManager::add_entity(std::string_view name,
send_entity_create(e, name, pos); send_entity_create(e, name, pos);
} }
void ServerEntityManager::send_all_entities(std::shared_ptr<Session>& session) {
auto view = m_registry.view<Entity, EntityInfo, BaseServerCreature>();
for (auto& entity : view) {
auto [e, info, base] =
view.get<Entity, EntityInfo, BaseServerCreature>(entity);
Arena arena;
auto* s2c = Arena::Create<S2CEntityCreate>(&arena);
s2c->set_id(e.id);
s2c->set_name(info.name);
Tools::set_net_pos(s2c, base.transform.position.value);
session->send(make_packet(*s2c));
}
}
void ServerEntityManager::send_entity_create(EntityID id, std::string_view name, void ServerEntityManager::send_entity_create(EntityID id, std::string_view name,
const glm::vec3& pos) { const glm::vec3& pos) {
auto sessions = m_world.get_all_session(); auto sessions = m_world.get_all_session();

View File

@@ -178,7 +178,7 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid,
void ServerWorld::init_world() { void ServerWorld::init_world() {
m_entity_manager.init(); m_entity_manager.init();
m_entity_manager.add_entity("cubed:pig", {0, 90, 0});
register_timer("player disconnect", 5, [this]() { register_timer("player disconnect", 5, [this]() {
std::vector<std::string> disconnect; std::vector<std::string> disconnect;
{ {
@@ -527,6 +527,7 @@ void ServerWorld::hot_reload() {
void ServerWorld::update() { void ServerWorld::update() {
// poll_finished_chunks(); // poll_finished_chunks();
m_entity_manager.update();
{ {
bool consumed = false; bool consumed = false;
std::unique_ptr<ServerChunk> chunk; std::unique_ptr<ServerChunk> chunk;
@@ -706,6 +707,7 @@ void ServerWorld::handle_player_login(const std::string& name,
boardcast_message("Server", std::format("Player {} Join Game", name), boardcast_message("Server", std::format("Player {} Join Game", name),
Color::YELLOW, true); Color::YELLOW, true);
m_entity_manager.handle_player_login(session);
} }
void ServerWorld::handle_player_exit(const std::string& uuid) { void ServerWorld::handle_player_exit(const std::string& uuid) {

View File

@@ -82,7 +82,7 @@ asio::awaitable<void> Session::read_loop() {
} }
if (cmd_id == std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ)) { if (cmd_id == std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ)) {
auto* req = Arena::Create<BlockChangeReq>(&arena); auto* req = Arena::Create<BlockChangeReq>(&arena);
Logger::info("Session: Receive Block Change req");
if (decode_packet(*req, body_data, header)) { if (decode_packet(*req, body_data, header)) {
m_server_world.handle_block_change(*req); m_server_world.handle_block_change(*req);
} }

View File

@@ -34,7 +34,7 @@ ModelManager::Handle ModelManager::get_model(const std::string& model_name) {
ModelManager::Handle ModelManager::get_model(ModelID id) { ModelManager::Handle ModelManager::get_model(ModelID id) {
ModelMap::const_accessor cacc; ModelMap::const_accessor cacc;
if (!m_models.find(cacc, id)) { if (m_models.find(cacc, id)) {
return {cacc->second, cacc->first}; return {cacc->second, cacc->first};
} }
return load_model(get_model_name(id)); return load_model(get_model_name(id));
@@ -73,10 +73,10 @@ ModelManager::Handle ModelManager::load_model(std::string_view model_name) {
} }
std::string path; std::string path;
if (space[0] == "cubed") { if (space[0] == "cubed") {
path = std::format("{}model/creature/{}/{}.gbl", ASSETS_PATH, space[1], path = std::format("{}model/creature/{}/{}.glb", ASSETS_PATH, space[1],
space[1]); space[1]);
} else { } else {
path = std::format("./{}/model/creature/{}/{}.gbl", space[0], space[1], path = std::format("./{}/model/creature/{}/{}.glb", space[0], space[1],
space[1]); space[1]);
} }
auto model = m_loader.load(path); auto model = m_loader.load(path);

View File

@@ -3,6 +3,7 @@
#include "Cubed/camera.hpp" #include "Cubed/camera.hpp"
#include "Cubed/debug_collector.hpp" #include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/client_world.hpp" #include "Cubed/gameplay/client_world.hpp"
#include "Cubed/gameplay/ecs/client_entity.hpp"
#include "Cubed/render/renderer.hpp" #include "Cubed/render/renderer.hpp"
#include "Cubed/render/renderer_constants.hpp" #include "Cubed/render/renderer_constants.hpp"
#include "Cubed/scene/world_scene.hpp" #include "Cubed/scene/world_scene.hpp"
@@ -294,14 +295,15 @@ void WorldRenderer::shadow_entity(ClientWorld& world,
auto& shader = m_renderer.get_shader("depth_model"); auto& shader = m_renderer.get_shader("depth_model");
shader.use(); shader.use();
shader.set_loc("lightSpaceMatrix", light_matrix); shader.set_loc("lightSpaceMatrix", light_matrix);
/* glEnable(GL_DEPTH_TEST);
auto& registry = world.get_registry(); auto& registry = world.entity_manager().get_registry();
auto view = registry.view<Transform, Model>(); auto view = registry.view<BaseClientCreature>();
for (auto entity : view) { for (auto entity : view) {
auto [transform, model] = view.get<Transform, Model>(entity); auto& creature = view.get<BaseClientCreature>(entity);
m_renderer.model_renderer().shadow_pass(model.name, transform.pos, m_renderer.model_renderer().shadow_pass(
world.world_scene().camera()); creature.model, creature.transform.position.value,
}*/ world.world_scene().camera());
}
m_player_renderer.render(shader, world, true); m_player_renderer.render(shader, world, true);
} }
@@ -714,15 +716,15 @@ void WorldRenderer::render_entity(ClientWorld& world) {
m_depth_map_texture->bind(0); m_depth_map_texture->bind(0);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
/*
auto& registry = world.get_registry(); auto& registry = world.entity_manager().get_registry();
auto view = registry.view<Transform, Model>(); auto view = registry.view<BaseClientCreature>();
for (auto entity : view) { for (auto entity : view) {
auto [transform, model] = view.get<Transform, Model>(entity); auto& creature = view.get<BaseClientCreature>(entity);
m_renderer.model_renderer().render_model(model.name, transform.pos, m_renderer.model_renderer().render_model(
world.world_scene().camera()); creature.model, creature.transform.position.value,
world.world_scene().camera());
} }
*/
m_player_renderer.render(shader, world, false); m_player_renderer.render(shader, world, false);
} }