#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
@@ -1,92 +1,126 @@
#pragma once
#include <imgui.h>
#include <implot.h>
#include <algorithm>
#include <chrono>
#include <format>
#include <ratio>
#include <cstddef>
#include <vector>
#include "debug/ComponentGui.hpp"
#include <implot.h>
#include <implot_internal.h>
#include "network/EntityInterpolation.hpp"
#include "world/Transform.hpp"
template<>
class tw::dbg::ComponentGui<tw::net::EntityPositionInterpolation> {
class tw::dbg::ComponentGui<tw::Transform> {
private:
using Clock = std::chrono::high_resolution_clock;
/** Ten seconds of frames at sixty a second. */
static constexpr size_t CAPACITY = 600;
/** How much of the trace the plot shows. */
static constexpr float WINDOW_IN_SECONDS = 10.0f;
inline static const Transform* m_subject = nullptr;
inline static int m_last_frame = -1;
inline static Clock::time_point m_started_at {};
inline static size_t m_head = 0;
inline static size_t m_count = 0;
inline static std::vector<float> m_times;
inline static std::vector<float> m_positions;
void restart(const Transform* instance) {
m_subject = instance;
m_last_frame = -1;
m_started_at = Clock::now();
m_head = 0;
m_count = 0;
m_times.resize(CAPACITY);
m_positions.resize(CAPACITY);
}
/** Keeps one sample per frame, so drawing twice does not double up. */
void sample(float time, float position) {
const int frame = ImGui::GetFrameCount();
if(m_last_frame == frame) {
return;
}
m_last_frame = frame;
m_times[m_head] = time;
m_positions[m_head] = position;
m_head = (m_head + 1) % CAPACITY;
m_count = std::min(m_count + 1, CAPACITY);
}
public:
void draw(net::EntityPositionInterpolation* instance) {
ImGui::SeparatorText("Entity Interpolation");
void draw(Transform* instance) {
ImGui::SeparatorText("Drawn Position");
bool m_is_scrolling = true;
static float m_metric_history = 10.0f;
ImGui::Checkbox("Is Scrolling", &m_is_scrolling);
if(m_is_scrolling) {
ImGui::SliderFloat("History", &m_metric_history,1,30,"%.1f s");
static int axis = 0;
ImGui::Combo("Axis", &axis, "X\0Y\0Z\0");
if(m_subject != instance) {
restart(instance);
}
static int32_t time_buffer_len = 1000;
ImGui::SliderInt("Buffer length (ms)", &time_buffer_len, 0, 1000);
const float time = std::chrono::duration<float>(Clock::now() - m_started_at).count();
const float position = instance->position()[axis];
auto now = std::chrono::high_resolution_clock::now() - std::chrono::milliseconds(time_buffer_len);
auto [from, to, mix] = instance->get_values_around(now);
auto mixed_value = glm::mix(from, to, mix);
sample(time, position);
ImGui::Text("Mixed value: (%f, %f)", mixed_value.x, mixed_value.y);
ImGui::Text("%.3f", position);
if(ImPlot::BeginPlot("Interpolation")) {
auto plot_x_from = instance->times().begin()->time_since_epoch().count();
std::vector<float> times(instance->times().max_size());
for(auto it = instance->times().begin(); it != instance->times().end(); ++it) {
times.push_back(it->time_since_epoch().count() - plot_x_from);
}
if(ImPlot::BeginPlot("Position", ImVec2(-1.0f, 180.0f))) {
ImPlot::SetupAxes("seconds", "position", ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisLimits(ImAxis_X1,
time - WINDOW_IN_SECONDS, time, ImGuiCond_Always);
float interpolated_time = now.time_since_epoch().count() - plot_x_from;
std::vector<float> values(instance->values().max_size());
for(auto it = instance->values().begin(); it != instance->values().end(); ++it) {
values.push_back(it->x);
}
values.push_back(mixed_value.x);
// The trace wraps around, so it is read from wherever the oldest
// sample ended up.
const int offset = m_count == CAPACITY ? (int)m_head : 0;
ImPlot::PlotLine("Drawn", m_times.data(), m_positions.data(),
(int)m_count, ImPlotLineFlags_None, offset);
ImPlot::PlotScatter("Server", times.data(), values.data(), times.size() - 1);
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1));
ImPlot::PlotScatter("Client", &interpolated_time, &mixed_value.x, 1);
ImPlot::PopStyleVar();
ImPlot::EndPlot();
}
if(ImGui::BeginTable("entityInterpolation", 2)) {
for(int i = 0; i < instance->values().size(); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
auto value = instance->values()[i];
auto now = std::chrono::high_resolution_clock::now() - std::chrono::milliseconds(time_buffer_len);
if(instance->times()[i] > now && (i == instance->values().size() - 1 || instance->times()[i + 1] <= now)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, ImGui::GetColorU32(ImVec4(0.7f, 0.3f, 0.3f, 0.65f)));
ImGui::Text("%f %f %f", mixed_value.x, mixed_value.y, mixed_value.z);
ImGui::TableNextColumn();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.0f)));
}
ImGui::Text("%f %f %f", value.x, value.y, value.z);
auto point = instance->times()[i].time_since_epoch();
auto hours = std::chrono::duration_cast<std::chrono::hours>(point);
point -= hours;
hours %= 24;
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(point);
point -= minutes;
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(point);
point -= seconds;
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(point);
ImGui::TableNextColumn();
auto fmt = std::format("{}:{}:{}.{}", hours, minutes, seconds, milliseconds);
ImGui::Text(fmt.c_str());
}
ImGui::EndTable();
}
}
};
/**
* Says whether the position above stands still because nothing new arrives for
* the entity, or because what arrives is the same position over and over.
*/
template<>
class tw::dbg::ComponentGui<tw::net::EntityPositionInterpolation> {
private:
using Clock = std::chrono::high_resolution_clock;
static int64_t millis_since(Clock::time_point point) {
return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - point).count();
}
public:
void draw(net::EntityPositionInterpolation* instance) {
ImGui::SeparatorText("Received Positions");
const size_t newest = instance->values().size() - 1;
ImGui::Text("Newest %.3f %.3f %.3f, %ld ms ago",
instance->values()[newest].x,
instance->values()[newest].y,
instance->values()[newest].z,
millis_since(instance->times()[newest]));
ImGui::Text("Oldest %.3f %.3f %.3f, %ld ms ago",
instance->values()[0].x,
instance->values()[0].y,
instance->values()[0].z,
millis_since(instance->times()[0]));
}
};
@@ -50,7 +50,8 @@ void EntityManagerGui::draw_entity_components() {
ImGui::Text("Vertex offset: %i", mesh->vertex_offset());
}
draw_debug_components<tw::CharacterBody, tw::CharacterController, tw::net::EntityPositionInterpolation>();
draw_debug_components<tw::Transform, tw::net::EntityPositionInterpolation,
tw::CharacterBody, tw::CharacterController>();
// auto character = m_world->registry().try_get<tw::CharacterBody>(m_selected);
// if(character != nullptr) {