This commit is contained in:
Martin Slachta
2026-07-18 14:31:15 +02:00
commit a04f0dc262
3343 changed files with 1140208 additions and 0 deletions
@@ -0,0 +1,20 @@
#pragma once
#include <imgui.h>
#include "debug/ComponentGui.hpp"
#include "world/CharacterBody.hpp"
template<>
class tw::dbg::ComponentGui<tw::CharacterBody> {
public:
void draw(CharacterBody* instance) {
ImGui::SeparatorText("Character Body Component");
auto position = instance->m_character->GetPosition();
ImGui::InputFloat3("Position", (float*)&position);
auto new_velocity = instance->m_character->GetLinearVelocity();
ImGui::InputFloat3("New Velocity", (float*)&new_velocity);
}
};
@@ -0,0 +1,43 @@
#pragma once
#include <imgui.h>
#include "debug/ComponentGui.hpp"
#include "world/CharacterController.hpp"
template<>
class tw::dbg::ComponentGui<tw::CharacterController> {
public:
void draw(tw::CharacterController* instance) {
ImGui::SeparatorText("Character Controller Component");
if(ImGui::BeginTable("history", 3)) {
for(auto key : instance->input_history().buffer()) {
// ImGui::TableNextRow();
// ImGui::TableNextColumn();
// ImGui::Text("%u", key.first);
// ImGui::TableNextColumn();
// if(instance->input_history().get(key.first).has_value()) {
// glm::vec3 input_vec = *instance->input_history().get(key.first).value();
// std::string input = std::format("{} {} {}", input_vec.x, input_vec.y, input_vec.z);
// ImGui::Text("%s", input.c_str());
// } else {
// ImGui::Text("None");
// }
// ImGui::TableNextColumn();
// if(instance->position_history().get(key).has_value()) {
// glm::vec3 position_vec = *instance->position_history().get(key).value();
// std::string position = std::format("{} {} {}", position_vec.x, position_vec.y, position_vec.z);
// ImGui::Text("%s", position.c_str());
// } else {
// ImGui::Text("None");
// }
}
ImGui::EndTable();
}
}
};
+11
View File
@@ -0,0 +1,11 @@
#pragma once
namespace tw::dbg {
template<typename T>
class ComponentGui {
public:
void draw(T* instance);
};
}
@@ -0,0 +1,21 @@
#pragma once
#include <string>
namespace tw::dbg {
class DebugComponent {
private:
std::string m_name;
public:
inline const std::string& name() const {
return m_name;
}
DebugComponent(const std::string& name) :
m_name{name} {
}
};
}
@@ -0,0 +1,92 @@
#pragma once
#include <imgui.h>
#include <chrono>
#include <format>
#include <ratio>
#include "debug/ComponentGui.hpp"
#include <implot.h>
#include <implot_internal.h>
#include "network/EntityInterpolation.hpp"
template<>
class tw::dbg::ComponentGui<tw::net::EntityPositionInterpolation> {
public:
void draw(net::EntityPositionInterpolation* instance) {
ImGui::SeparatorText("Entity Interpolation");
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 int32_t time_buffer_len = 1000;
ImGui::SliderInt("Buffer length (ms)", &time_buffer_len, 0, 1000);
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);
ImGui::Text("Mixed value: (%f, %f)", mixed_value.x, mixed_value.y);
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);
}
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);
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();
}
}
};
@@ -0,0 +1,94 @@
#include "EntityManagerGui.hpp"
#include "debug/tools/MatrixWidget.hpp"
#include "debug/EntityInterpolationDebug.hpp"
#include "debug/CharacterBodyDebug.hpp"
#include "debug/CharacterControllerDebug.hpp"
#include "network/EntityInterpolation.hpp"
#include "world/CharacterBody.hpp"
#include "world/CharacterController.hpp"
#include "world/Transform.hpp"
#include "world/WorldEntity.hpp"
#include "draw/Mesh.hpp"
#include <imgui.h>
#include <entt/entity/entity.hpp>
#include <spdlog/spdlog.h>
namespace tw::dbg::tools {
EntityManagerGui::EntityManagerGui(World* world) :
m_world(world) {
}
void draw_transform_controls(Transform* transform) {
}
void EntityManagerGui::draw_entity_components() {
ImGui::Text("Entity ID: %d", (uint32_t)m_selected);
ImGui::BeginChild("Transform Component", ImVec2(0, 0), ImGuiChildFlags_Border);
auto transform = m_world->registry().try_get<Transform>(m_selected);
if(transform != nullptr) {
ImGui::SeparatorText("Transform Component");
draw_matrix_widget(transform->transform, true);
draw_transform_controls(transform);
}
auto mesh = m_world->registry().try_get<tw::drw::Mesh>(m_selected);
if(mesh != nullptr) {
ImGui::SeparatorText("Mesh Component");
ImGui::Text("Num vertices: %i", mesh->num_vertices());
ImGui::Text("Vertex offset: %i", mesh->vertex_offset());
}
draw_debug_components<tw::CharacterBody, tw::CharacterController, tw::net::EntityPositionInterpolation>();
// auto character = m_world->registry().try_get<tw::CharacterBody>(m_selected);
// if(character != nullptr) {
// tw::dbg::ComponentGui<tw::CharacterBody>().draw(character);
// }
//
// auto entity_interpolation = m_world->registry().try_get<EntityInterpolation>(m_selected);
// if(entity_interpolation != nullptr) {
// tw::dbg::ComponentGui<EntityInterpolation>().draw(entity_interpolation);
// }
ImGui::EndChild();
}
void EntityManagerGui::draw() {
ImGui::Begin("Transforms");
ImGui::BeginChild("Entities", ImVec2(0, 260), ImGuiChildFlags_Border);
ImGui::SeparatorText("Entities");
auto view = m_world->registry().view<const WorldEntity>();
view.each([&](const auto entity, const WorldEntity& info) {
ImGui::PushID((uint32_t)info.entity_id);
if(ImGui::Selectable(info.name.c_str(), m_selected_entity == info.entity_id)) {
m_selected_entity = info.entity_id;
m_selected = entity;
}
ImGui::PopID();
});
ImGui::EndChild();
if(m_selected_entity.has_value()) {
draw_entity_components();
}
ImGui::End();
}
}
@@ -0,0 +1,42 @@
#pragma once
#include "debug/ComponentGui.hpp"
#include "entt/entity/fwd.hpp"
#include "world/World.hpp"
#include <entt/entt.hpp>
namespace tw::dbg::tools {
class EntityManagerGui {
private:
World* m_world;
entt::entity m_selected;
std::optional<uint32_t> m_selected_entity;
void draw_entity_components();
template<class... Ts>
void draw_debug_components() {
(
[&]() {
auto character = m_world->registry().try_get<Ts>(m_selected);
if(character != nullptr) {
tw::dbg::ComponentGui<Ts>().draw(character);
}
}(),
...);
}
public:
entt::entity& selected() {
return m_selected;
}
EntityManagerGui(World* world);
void draw();
};
}
@@ -0,0 +1,27 @@
#include "MatrixWidget.hpp"
namespace tw::dbg::tools {
void draw_matrix_widget(const glm::mat4 &mat, bool display_headers) {
if(ImGui::BeginTable("transform_component", 4, ImGuiTableFlags_Borders)) {
if(display_headers) {
ImGui::TableSetupColumn("Right");
ImGui::TableSetupColumn("Front");
ImGui::TableSetupColumn("Up");
ImGui::TableSetupColumn("Translation");
ImGui::TableHeadersRow();
}
for(int row = 0; row < 4; row++) {
ImGui::TableNextRow();
for(int col = 0; col < 4; col++) {
ImGui::TableSetColumnIndex(col);
ImGui::Text("%f", mat[col][row]);
}
}
ImGui::EndTable();
}
}
}
@@ -0,0 +1,10 @@
#pragma once
#include <glm/glm.hpp>
#include <imgui.h>
namespace tw::dbg::tools {
void draw_matrix_widget(const glm::mat4& mat, bool display_headers);
}
@@ -0,0 +1,101 @@
#pragma once
#include <imgui.h>
#include <implot.h>
#include <implot_internal.h>
#include "metrics/BucketMetric.hpp"
namespace tw::dbg::tools {
template<typename T, typename Interval, typename Op = net::SumOp<T>>
class MetricWidget {
std::string m_name;
net::BucketMetric<T, Interval, Op>& m_metric;
using Self = MetricWidget<T, Interval, Op>;
struct {
T constraint_from;
T constraint_to;
T from;
T to;
} y_axis;
bool m_is_scrolling = true;
public:
MetricWidget(
const std::string& name,
net::BucketMetric<T, Interval, Op>& metric
) :
m_name(name),
m_metric(metric)
{
y_axis = {
.constraint_from = 0,
.constraint_to = 500,
.from = 0,
.to = 250
};
}
Self& set_x_axis_limits_contraints(T from, T to) {
ImPlot::SetupAxisLimits(ImAxis_X1, from, to);
return *this;
}
Self& set_y_axis_limits(double from, double to) {
return *this;
}
Self& enable_scrolling() {
m_is_scrolling = true;
}
Self& disable_scrolling() {
m_is_scrolling = true;
}
void draw() {
ImGui::PushID(m_name.c_str());
auto head = m_metric.get_head();
auto head_timeline = m_metric.get_head_timeline();
auto tail = m_metric.get_tail();
auto tail_timeline = m_metric.get_tail_timeline();
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");
}
ImGui::Text("Min: %i", m_metric.min());
ImGui::Text("Max: %i", m_metric.max());
if(ImPlot::BeginPlot(m_name.c_str())) {
auto from = tail_timeline.empty() ? *(head_timeline.end() - 1) : *(tail_timeline.end() - 1);
ImPlot::SetupAxes("Time", m_name.c_str(), ImPlotAxisFlags_None, ImPlotAxisFlags_None);
if(m_is_scrolling) {
ImPlot::SetupAxisLimits(ImAxis_X1, from - m_metric_history, from, ImGuiCond_Always);
}
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, m_metric.max() * 2, ImGuiCond_Always);
// ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, 10000);
ImPlot::PlotLine(m_name.c_str(), head_timeline.data(), head.data(), head.size());
ImPlot::PlotLine(m_name.c_str(), tail_timeline.data(), tail.data(), tail.size());
ImPlot::EndPlot();
}
ImGui::PopID();
}
};
}
@@ -0,0 +1,62 @@
#pragma once
#include "metrics/BucketMetric.hpp"
#include "metrics/NetworkStatsLogger.hpp"
#include "debug/tools/MetricWidget.hpp"
#include <implot.h>
#include <implot_internal.h>
namespace tw::dbg::tools {
class NetworkStatsGui {
private:
// MetricWidget<uint32_t, std::chrono::seconds, net::AverageOp<uint32_t>> m_ping_widget;
// MetricWidget<uint32_t, std::chrono::seconds> m_outgoing_widget;
// MetricWidget<uint32_t, std::chrono::seconds> m_incoming_widget;
public:
// NetworkStatsGui() :
// m_ping_widget("Ping", net::NetworkStatsLogger::instance()->ping()),
// m_outgoing_widget("Outgoing", net::NetworkStatsLogger::instance()->outgoing()),
// m_incoming_widget("Incoming", net::NetworkStatsLogger::instance()->incoming())
// {
// }
void draw() {
// auto* instance = net::NetworkStatsLogger::instance();
// auto& ping = instance->ping();
ImGui::Begin("Network Stats");
/* auto head = ping.get_head();
auto head_timeline = ping.get_head_timeline();
auto tail = ping.get_tail();
auto tail_timeline = ping.get_tail_timeline();
static float ping_history = 10.0f;
ImGui::SliderFloat("Ping History", &ping_history,1,30,"%.1f s");
if(ImPlot::BeginPlot("Ping")) {
auto from = tail_timeline.empty() ? *(head_timeline.end() - 1) : *(tail_timeline.end() - 1);
ImPlot::SetupAxes("FrameIdx","FPS", ImPlotAxisFlags_None, ImPlotAxisFlags_None);
ImPlot::SetupAxisLimits(ImAxis_X1, from - ping_history, from, ImGuiCond_Always);
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 120);
ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, 10000);
ImPlot::PlotLine("Ping", head_timeline.data(), head.data(), head.size());
ImPlot::PlotLine("Ping", tail_timeline.data(), tail.data(), tail.size());
ImPlot::EndPlot();
} */
// m_ping_widget.draw();
// m_outgoing_widget.draw();
// m_incoming_widget.draw();
ImGui::End();
}
};
}
@@ -0,0 +1,57 @@
#pragma once
#include <format>
#include <vector>
#include "imgui.h"
#include "metrics/NetworkStatsLogger.hpp"
namespace tw::dbg::tools {
class PacketBacklogGui {
private:
std::vector<uint32_t> m_buckets;
uint32_t m_last_backlog_idx;
public:
void draw() {
// auto* instance = net::NetworkStatsLogger::instance();
// size_t size = instance->get_size();
// if(ImGui::BeginTable("Network Packets", 5)) {
// ImGui::TableSetupColumn("Message Type");
// ImGui::TableSetupColumn("Time");
// ImGui::TableSetupColumn("Is From Us");
// ImGui::TableSetupColumn("Target");
// ImGui::TableSetupColumn("Size");
// for(int32_t i = size-1; i >= 0; i--) {
// auto& item = instance->get_item(i);
// ImGui::PushID(item.timepoint.time_since_epoch().count());
// ImGui::TableNextRow();
// ImGui::TableNextColumn();
// ImGui::Text("%i", item.message_type);
// ImGui::TableNextColumn();
// ImGui::Text(std::format("{}", item.timepoint.time_since_epoch()).c_str());
// ImGui::TableNextColumn();
// ImGui::Checkbox("is_sent_from_us", &item.is_sent_by_us);
// ImGui::TableNextColumn();
// ImGui::Text(item.target.to_string().c_str());
// ImGui::TableNextColumn();
// ImGui::Text("%ld", item.buffer.size());
// ImGui::PopID();
// }
// ImGui::EndTable();
// }
}
};
}
@@ -0,0 +1,55 @@
#pragma once
#include "Address.hpp"
#include "ByteBuffer.hpp"
#include "packets/Packet.hpp"
#include <cstring>
namespace tw::dbg::tools {
class SavedPacket {
public:
PacketType type;
bool is_from_client;
net::Address origin;
net::Address target;
net::ByteBuffer content;
public:
SavedPacket() :
origin({}, 0),
target({}, 0),
content(1024)
{
}
};
class PacketLogger {
private:
std::vector<SavedPacket> m_packets;
uint32_t m_left, m_right;
public:
inline const std::vector<SavedPacket>& packets() const {
return m_packets;
}
PacketLogger(uint32_t max_packets) :
m_packets(max_packets) {
}
void push(PacketType type, bool is_from_client,
net::Address origin, net::Address target,
net::ByteBuffer& content) {
SavedPacket& packet = m_packets[m_right];
packet.type = type;
packet.is_from_client = is_from_client;
packet.origin = origin;
packet.target = target;
memcpy(packet.content.data().data(), content.data().data(), content.writing_head());
}
};
}
@@ -0,0 +1,41 @@
#include "PerformanceStatsGui.hpp"
#include <imgui.h>
#include <implot.h>
#include <implot_internal.h>
namespace tw::dbg::tools {
PerformanceStatsGui::PerformanceStatsGui(LockStep& lock_step) :
m_lockstep(lock_step),
fps_history(1000),
frame_idxs(1000)
{
}
void PerformanceStatsGui::draw() {
ImGui::Begin("Stats");
ImGui::Text("FPS: %ld", m_lockstep.fps());
fps_history[fps_history_idx] = m_lockstep.fps();
frame_idxs[fps_history_idx] = frame_idx++;
if(fps_history_idx == fps_history.size() - 1) {
is_plot_filled = true;
}
fps_history_idx = (fps_history_idx + 1) % fps_history.size();
if(ImPlot::BeginPlot("FPS Plot")) {
ImPlot::SetupAxes("FrameIdx","FPS", ImPlotAxisFlags_None, ImPlotAxisFlags_None);
ImPlot::SetupAxisLimits(ImAxis_X1, std::max((double)frame_idx - 1000.0, 0.0), frame_idx, ImGuiCond_Always);
ImPlot::SetupAxisLimits(ImAxis_Y1,0,120);
ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, 120);
ImPlot::PlotLine("FPS", frame_idxs.data(), fps_history.data(), is_plot_filled ? (int)fps_history.size() : (int)fps_history_idx - 1);
ImPlot::EndPlot();
}
ImGui::End();
}
}
@@ -0,0 +1,24 @@
#pragma once
#include "runtime/LockStep.hpp"
namespace tw::dbg::tools {
class PerformanceStatsGui {
private:
LockStep& m_lockstep;
std::vector<uint32_t> fps_history;
std::vector<uint32_t> frame_idxs;
uint32_t fps_history_idx = 0;
uint32_t frame_idx = 0;
bool is_plot_filled = false;
public:
PerformanceStatsGui(LockStep& lock_step);
void draw();
};
}