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

View File

@@ -22,6 +22,7 @@
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -92,7 +93,7 @@ public:
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;
ServerEntityManager& entity_manager();
@@ -114,7 +115,27 @@ private:
struct ChunkEntity {
ChunkState state;
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 };

View File

@@ -33,18 +33,31 @@ void ServerEntityManager::update() {
handle_task();
auto view = m_registry.view<BaseServerCreature>();
auto sessions = m_world.get_all_session();
for (auto e : view) {
const auto& c = view.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);
continue;
}
update_ai(e);
update_move(e);
update_send(e, sessions);
auto pool = m_world.get_compute_pool();
if (!pool) {
return;
}
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) {
@@ -59,7 +72,8 @@ void ServerEntityManager::update_move(entt::entity e) {
}
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)) {
return;

View File

@@ -182,7 +182,6 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid,
void ServerWorld::init_world(RunMode mode) {
m_runmode = mode;
m_entity_manager.init();
m_entity_manager.add_entity("cubed:pig", {0, 225, 0});
register_timer("player disconnect", 5, [this]() {
std::vector<std::string> disconnect;
{
@@ -319,7 +318,7 @@ void ServerWorld::sync_and_collect_missing_chunks(
chunk_acc acc;
if (m_chunks.insert(acc, 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(); }
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::vector<std::shared_ptr<Session>> sessions;
tbb::concurrent_vector<std::shared_ptr<Session>> sessions;
for (const auto& [_, player] : m_players) {
sessions.emplace_back(player.get_session());
}