#1 - ClientWorldController refactoring

This commit is contained in:
Martin Slachta
2026-08-03 23:30:18 +02:00
parent f4174eb0c7
commit a097f4d4b0
35 changed files with 899 additions and 1010 deletions
@@ -0,0 +1,242 @@
#include "ClientWorldController.hpp"
#include <glm/glm.hpp>
#include <chrono>
#include <spdlog/spdlog.h>
#include <span>
#include "network/ServerConnection.hpp"
#include "Entity.pb.h"
#include "Login.pb.h"
#include "WorldState.pb.h"
#include "PlayerMove.pb.h"
#include "entt/entity/entity.hpp"
#include "entt/entity/fwd.hpp"
#include "messages/PlayerMoveMessage.hpp"
#include "metrics/HistoryBuffer.hpp"
#include "world/CharacterBody.hpp"
#include "world/CharacterController.hpp"
#include "world/JoltPhysicsWorld.hpp"
#include "world/WorldEntity.hpp"
#include "tw/serial/WorldStateWriter.hpp"
#include "network/EntityInterpolation.hpp"
namespace tw {
typedef HistoryBuffer<long, glm::vec3> EntityPositionHistory;
entt::entity
ClientWorldController::create_entity(const std::string& name, glm::vec3 position) {
const auto entity = m_world->registry().create();
if(!m_mesh.has_value()) {
m_mesh = m_world_renderer->add_mesh(drw::MeshData::cube(glm::vec3(1.0f)));
}
spdlog::info("Creating entity {}", name);
m_world->registry()
.emplace<Transform>(entity,
Transform(position));
m_world->registry()
.emplace<tw::WorldEntity>(entity,
tw::WorldEntity(name, (uint32_t)entity));
m_world->registry()
.emplace<tw::drw::Mesh>(entity,
m_mesh.value());
return entity;
}
// void ClientWorldController::apply_entity_positions(const mmo::EntityPosition* const* positions, size_t count) {
// for(int i = 0; i < count; i++) {
// const mmo::EntityPosition* const position = positions[i];
// std::optional<entt::entity> entity = m_entity_id_map.get_local(position->id());
//
// if(!entity.has_value()) {
// spdlog::warn("Received position update for unknown entity {}", position->id());
// continue;
// }
//
// glm::vec3 p = {position->x(), position->y(), position->z()};
// m_entity_interpolator.add_position_for_entity(entity.value(), p);
//
// EntityPositionHistory* history = m_world->registry().try_get<EntityPositionHistory>(entity.value());
//
// if(history != nullptr) {
// auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now().time_since_epoch()).count();
// history->set(millis, p);
// }
// }
// }
void ClientWorldController::spawn_entity(const std::string& name, uint32_t server_id) {
auto entity = create_entity(name, glm::vec3());
spdlog::info("Spawning entity {}", server_id);
m_entity_id_map.register_server_to_local(server_id, entity);
if(m_controlled_server_id.has_value() && m_controlled_server_id.value() == server_id) {
try_bind_player_entity();
} else {
}
m_interpolator.register_entity(entity);
}
ClientWorldController::ClientWorldController(
const io::InputManager* inputs,
World* world,
JoltPhysicsWorld* physics_world,
drw::WorldRenderer* world_renderer,
net::ServerConnection* connection,
dbg::NetworkMetrics* network_metrics
) :
m_input_manager(inputs),
m_world(world),
m_physics_world(physics_world),
m_world_renderer(world_renderer),
m_player_controller(&world_renderer->camera(), glm::vec3()),
m_connection(connection),
m_messages(connection->endpoint()),
m_network_metrics(network_metrics),
m_input_update_tick_step(20),
m_interpolator(&m_world->registry(), 300),
m_replicator_client(&m_messages, &m_entity_id_map, m_network_metrics, m_physics_world, m_world, m_connection, &m_interpolator)
{
m_messages.set_handler<mmo::LoginResponse>(
[this](msg::PeerId, const mmo::LoginResponse& mesg) {
spdlog::info("Logged in!");
});
m_messages.set_handler<mmo::SetControlledEntity>(
[this](msg::PeerId, const mmo::SetControlledEntity& mesg) {
spdlog::info("Setting controlled entity from server id {}", mesg.entity_id());
m_controlled_server_id = mesg.entity_id();
try_bind_player_entity();
});
m_connection->endpoint()->set_handler(Message<mmo::WorldStateMessage>::value,
[this](msg::PeerId, std::span<const std::byte> data) {
serial::WorldStateReader reader(data);
m_replicator_client.handle_snapshot(m_network_frame_idx, reader);
});
m_messages.set_handler<mmo::EntitySpawnMessage>(
[this](msg::PeerId, const mmo::EntitySpawnMessage& mesg) {
for(auto& spawn : mesg.spawns()) {
spawn_entity(spawn.name(), spawn.entity_id());
}
});
}
ClientWorldController::~ClientWorldController() { }
void ClientWorldController::try_bind_player_entity() {
if(!m_controlled_server_id.has_value()) {
return;
}
auto local_entity = m_entity_id_map.get_local(m_controlled_server_id.value());
if(!local_entity.has_value()) {
return;
}
if(m_player_entity.has_value() && m_player_entity.value() == local_entity.value()) {
return;
}
entt::entity entity = local_entity.value();
spdlog::info("Binding player entity");
m_player_entity = entity;
m_replicator_client.set_player_entity(entity);
Transform* transform = m_world->registry().try_get<Transform>(entity);
glm::vec3 position = transform ? transform->position() : glm::vec3(0.0f);
m_world->registry().emplace<CharacterController>(entity, 20.0f);
m_world->registry().emplace<CharacterBody>(entity, m_physics_world->create_character(
new JPH::BoxShape(JPH::Vec3Arg(0.5f, 0.5f, 0.5f)),
position
));
// if(m_world->registry().all_of<net::EntityPositionInterpolation>(entity)) {
// m_world->registry().remove<net::EntityPositionInterpolation>(entity);
// }
}
void ClientWorldController::update_network() {
auto network_start = Clock::now();
// collects incoming messages
m_connection->update();
m_network_metrics->record_update_time(Clock::now() - network_start);
m_network_metrics->sample({
.bytes_sent = m_connection->endpoint()->bytes_sent(),
.bytes_received = m_connection->endpoint()->bytes_received(),
.messages_sent = m_connection->endpoint()->messages_sent(),
.messages_received = m_connection->endpoint()->messages_received()
});
m_replicator_client.record_prediction(m_frame_idx);
// m_world->registry().view<Transform>()
// .each([&](const auto e, Transform& t) {
// m_position_history_exporter.write((uint32_t)e,
// std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now().time_since_epoch()).count(), t.position());
// });
}
void ClientWorldController::update(double delta_time) {
// collects messages from network for processing
update_network();
m_player_controller.update(m_input_manager, delta_time);
// collect input every tick of the input_update_tick_step
if(m_input_update_tick_step.has_ticked()) {
// TODO: might be more like `calc_velocity_vector`.
// 1. it is not exactly *input* but input mapped to normalized velocity vector
// 2. I hate calling methods `update`
glm::vec3 input = m_player_controller.input();
if(m_player_entity.has_value()) {
CharacterController* controller = m_world->registry().try_get<CharacterController>(m_player_entity.value());
if(controller) {
// controller->set_input(m_network_frame_idx, input);
m_replicator_client.set_input(m_network_frame_idx, input);
}
}
// m_physics_world->step(m_network_frame_idx, JoltPhysicsWorld::FIXED_DELTA_TIME, true);
m_network_frame_idx++;
}
m_interpolator.interpolate_smoothed_entities(std::chrono::high_resolution_clock::now() - std::chrono::milliseconds(500));
if(m_player_entity.has_value()) {
// after interpolation happen, follow the target
// TODO: The third person controller could pull
Transform* player_transform = m_world->registry().try_get<Transform>(m_player_entity.value());
if(player_transform) {
m_player_controller.set_target(player_transform->position());
}
}
m_frame_idx++;
// m_replicator_client.update();
}
}