#1 - ClientWorldController refactoring
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user