perf(server): parallelize entity update loop

Use parallel_do to process entities concurrently via the compute
pool. Switch get_all_session to a thread-safe
tbb::concurrent_vector and make ChunkEntity's ref_count atomic to
avoid data races. Also remove the debug pig spawn from world init.
This commit is contained in:
2026-08-05 20:26:57 +08:00
parent 2aa9d07e43
commit b2187bf0ad
4 changed files with 57 additions and 20 deletions

View File

@@ -5,6 +5,7 @@
#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> #include <tbb/concurrent_queue.h>
#include <tbb/concurrent_vector.h>
namespace Cubed { namespace Cubed {
class ServerWorld; class ServerWorld;
class Session; class Session;
@@ -46,8 +47,9 @@ private:
void send_all_entities(std::shared_ptr<Session>& session); void send_all_entities(std::shared_ptr<Session>& session);
void update_ai(entt::entity e); void update_ai(entt::entity e);
void update_move(entt::entity e); void update_move(entt::entity e);
void update_send(entt::entity e, void
std::span<std::shared_ptr<Session>> sessions); update_send(entt::entity e,
tbb::concurrent_vector<std::shared_ptr<Session>>& sessions);
template <typename... Args> template <typename... Args>
EntityID create_entity_in_factory(Args&&... args) { EntityID create_entity_in_factory(Args&&... args) {
auto entity = m_registry.create(); auto entity = m_registry.create();

View File

@@ -22,6 +22,7 @@
#include <tbb/concurrent_hash_map.h> #include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h> #include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h> #include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -92,7 +93,7 @@ public:
int chunk_size() const; int chunk_size() const;
std::vector<std::shared_ptr<Session>> get_all_session() const; tbb::concurrent_vector<std::shared_ptr<Session>> get_all_session() const;
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();
@@ -114,7 +115,27 @@ private:
struct ChunkEntity { struct ChunkEntity {
ChunkState state; ChunkState state;
std::shared_ptr<ServerChunk> chunk; std::shared_ptr<ServerChunk> chunk;
uint32_t ref_count = 0; std::atomic<uint32_t> ref_count = 0;
ChunkEntity() = default;
ChunkEntity(ChunkState s, std::shared_ptr<ServerChunk> c = {})
: state(s), chunk(std::move(c)) {}
ChunkEntity& operator=(ChunkEntity&& o) noexcept {
if (this == &o) {
return *this;
}
state = std::exchange(o.state, ServerWorld::ChunkState::NONE);
chunk = std::move(o.chunk);
ref_count = o.ref_count.exchange(0);
return *this;
}
ChunkEntity(ChunkEntity&& o) noexcept
: state(std::exchange(o.state, ServerWorld::ChunkState::NONE)),
chunk(std::move(o.chunk)), ref_count(o.ref_count.exchange(0)) {}
ChunkEntity(const ChunkEntity&) = delete;
ChunkEntity& operator=(const ChunkEntity&) = delete;
}; };
enum class ChunkLoadStyle { RANDOM, CENTER }; enum class ChunkLoadStyle { RANDOM, CENTER };

View File

@@ -33,18 +33,31 @@ void ServerEntityManager::update() {
handle_task(); handle_task();
auto view = m_registry.view<BaseServerCreature>(); auto view = m_registry.view<BaseServerCreature>();
auto sessions = m_world.get_all_session();
for (auto e : view) { auto pool = m_world.get_compute_pool();
const auto& c = view.get<BaseServerCreature>(e); if (!pool) {
if (!m_world.get_chunk_ref_count(c.transform.position.value)) { return;
const auto& entity = m_registry.get<Entity>(e);
destory(entity.id);
continue;
}
update_ai(e);
update_move(e);
update_send(e, sessions);
} }
auto sessions = m_world.get_all_session();
std::vector<entt::entity> entities;
for (auto e : view) {
entities.push_back(e);
}
// parallel block touches disjoint entities only;
// structural registry changes stay on the server thread via m_tasks.
parallel_do(
*pool, entities.begin(), entities.end(), pool->thread_sum(),
[this, &sessions](entt::entity e) {
const auto& c = m_registry.get<BaseServerCreature>(e);
if (!m_world.get_chunk_ref_count(c.transform.position.value)) {
const auto& entity = m_registry.get<Entity>(e);
destory(entity.id);
return;
}
update_ai(e);
update_move(e);
update_send(e, sessions);
});
} }
void ServerEntityManager::update_ai(entt::entity e) { void ServerEntityManager::update_ai(entt::entity e) {
@@ -59,7 +72,8 @@ void ServerEntityManager::update_move(entt::entity e) {
} }
void ServerEntityManager::update_send( void ServerEntityManager::update_send(
entt::entity e, std::span<std::shared_ptr<Session>> sessions) { entt::entity e,
tbb::concurrent_vector<std::shared_ptr<Session>>& sessions) {
if (!m_registry.all_of<Entity, BaseServerCreature>(e)) { if (!m_registry.all_of<Entity, BaseServerCreature>(e)) {
return; return;

View File

@@ -182,7 +182,6 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid,
void ServerWorld::init_world(RunMode mode) { void ServerWorld::init_world(RunMode mode) {
m_runmode = mode; m_runmode = mode;
m_entity_manager.init(); m_entity_manager.init();
m_entity_manager.add_entity("cubed:pig", {0, 225, 0});
register_timer("player disconnect", 5, [this]() { register_timer("player disconnect", 5, [this]() {
std::vector<std::string> disconnect; std::vector<std::string> disconnect;
{ {
@@ -319,7 +318,7 @@ void ServerWorld::sync_and_collect_missing_chunks(
chunk_acc acc; chunk_acc acc;
if (m_chunks.insert(acc, pos)) { if (m_chunks.insert(acc, pos)) {
need_gen_chunks_pos.push_back(pos); need_gen_chunks_pos.push_back(pos);
acc->second = ChunkEntity{ChunkState::GENERATING, nullptr, 0}; acc->second = ChunkEntity{ChunkState::GENERATING};
} }
} }
} }
@@ -1028,9 +1027,10 @@ void ServerWorld::set_chunk_load_style(int id) {
int ServerWorld::chunk_size() const { return m_chunks.size(); } int ServerWorld::chunk_size() const { return m_chunks.size(); }
std::vector<std::shared_ptr<Session>> ServerWorld::get_all_session() const { tbb::concurrent_vector<std::shared_ptr<Session>>
ServerWorld::get_all_session() const {
std::shared_lock lock(m_players_mutex); std::shared_lock lock(m_players_mutex);
std::vector<std::shared_ptr<Session>> sessions; tbb::concurrent_vector<std::shared_ptr<Session>> sessions;
for (const auto& [_, player] : m_players) { for (const auto& [_, player] : m_players) {
sessions.emplace_back(player.get_session()); sessions.emplace_back(player.get_session());
} }