implement debug ui + profiling
parent
16bcb94158
commit
eb65910d15
|
@ -17,6 +17,8 @@ add_executable(eco2d
|
|||
source/packet.c
|
||||
source/player.c
|
||||
source/signal_handling.c
|
||||
source/profiler.c
|
||||
source/debug_ui.c
|
||||
|
||||
source/utils/options.c
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include "system.h"
|
||||
|
||||
void debug_draw(void);
|
|
@ -6,5 +6,7 @@ void platform_shutdown();
|
|||
float platform_frametime();
|
||||
uint8_t platform_is_running();
|
||||
|
||||
float platform_zoom_get(void);
|
||||
|
||||
void platform_input();
|
||||
void platform_render();
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
#include "system.h"
|
||||
|
||||
typedef enum {
|
||||
PROF_MAIN_LOOP,
|
||||
PROF_WORLD_WRITE,
|
||||
PROF_RENDER,
|
||||
PROF_ENTITY_LERP,
|
||||
|
||||
MAX_PROF,
|
||||
PROF_FORCE_UINT8 = UINT8_MAX
|
||||
} profiler_kind;
|
||||
|
||||
typedef struct {
|
||||
profiler_kind id;
|
||||
char const *name;
|
||||
|
||||
uint32_t num_invocations;
|
||||
double start_time;
|
||||
double delta_time;
|
||||
double total_time;
|
||||
} profiler;
|
||||
|
||||
void profiler_reset(profiler_kind id);
|
||||
void profiler_start(profiler_kind id);
|
||||
void profiler_stop(profiler_kind id);
|
||||
void profiler_collate(void);
|
||||
|
||||
double profiler_delta(profiler_kind id);
|
||||
char const *profiler_name(profiler_kind id);
|
||||
|
||||
#define profile(id) defer(profiler_start(id), profiler_stop(id))
|
|
@ -1,8 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define ZPL_NANO
|
||||
#include "zpl.h"
|
||||
|
||||
#define defer_var ZPL_CONCAT(_i_,__LINE__)
|
||||
#define defer(s,e) for ( \
|
||||
uint32_t defer_var = (s, 0); \
|
||||
!defer_var; \
|
||||
(defer_var += 1), e) \
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "system.h"
|
||||
#include "raylib.h"
|
||||
#include "world/blocks.h"
|
||||
#include "assets.h"
|
||||
|
||||
static inline
|
||||
void DrawTextEco(const char *text, float posX, float posY, int fontSize, Color color, float spacing) {
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
#include "debug_ui.h"
|
||||
#include "raylib.h"
|
||||
|
||||
typedef enum {
|
||||
DITEM_RAW,
|
||||
DITEM_TEXT,
|
||||
DITEM_BUTTON,
|
||||
DITEM_SLIDER,
|
||||
DITEM_LIST,
|
||||
DITEM_END,
|
||||
|
||||
DITEM_FORCE_UINT8 = UINT8_MAX
|
||||
} debug_kind;
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} debug_draw_result;
|
||||
|
||||
#define DBG_FONT_SIZE 22
|
||||
#define DBG_FONT_SPACING DBG_FONT_SIZE * 1.2f
|
||||
#define DBG_START_XPOS 15
|
||||
#define DBG_START_YPOS 200
|
||||
#define DBG_LIST_XPOS_OFFSET 10
|
||||
#define DBG_SHADOW_OFFSET_XPOS 1
|
||||
#define DBG_SHADOW_OFFSET_YPOS 1
|
||||
|
||||
static uint8_t is_shadow_rendered;
|
||||
|
||||
typedef struct debug_item {
|
||||
debug_kind kind;
|
||||
char const *name;
|
||||
float name_width;
|
||||
union {
|
||||
union {
|
||||
char const *text;
|
||||
uint64_t val;
|
||||
};
|
||||
|
||||
struct {
|
||||
struct debug_item *items;
|
||||
uint8_t is_collapsed;
|
||||
} list;
|
||||
|
||||
void (*on_click)(void);
|
||||
};
|
||||
|
||||
debug_draw_result (*proc)(struct debug_item*, float, float);
|
||||
} debug_item;
|
||||
|
||||
typedef enum {
|
||||
DAREA_OUTSIDE,
|
||||
DAREA_HOVER,
|
||||
DAREA_HELD,
|
||||
DAREA_PRESS,
|
||||
|
||||
DAREA_FORCE_UINT8 = UINT8_MAX
|
||||
} debug_area_status;
|
||||
|
||||
debug_area_status check_mouse_area(float xpos, float ypos, float w, float h);
|
||||
bool is_btn_pressed(float xpos, float ypos, float w, float h, Color *color);
|
||||
|
||||
void UIDrawText(const char *text, float posX, float posY, int fontSize, Color color);
|
||||
int UIMeasureText(const char *text, int fontSize);
|
||||
|
||||
#include "debug_ui_widgets.c"
|
||||
|
||||
static debug_item items[] = {
|
||||
{
|
||||
.kind = DITEM_LIST,
|
||||
.name = "general",
|
||||
.list = {
|
||||
.items = (debug_item[]) {
|
||||
{ .kind = DITEM_TEXT, .name = "delta time", .proc = DrawDeltaTime },
|
||||
{ .kind = DITEM_TEXT, .name = "random literal", .text = "hello", .proc = DrawLiteral },
|
||||
{ .kind = DITEM_TEXT, .name = "zoom", .proc = DrawZoom },
|
||||
{ .kind = DITEM_END },
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.kind = DITEM_LIST,
|
||||
.name = "profilers",
|
||||
.list = {
|
||||
.items = (debug_item[]) {
|
||||
{ .kind = DITEM_RAW, .val = PROF_MAIN_LOOP, .proc = DrawProfilerDelta },
|
||||
{ .kind = DITEM_RAW, .val = PROF_WORLD_WRITE, .proc = DrawProfilerDelta },
|
||||
{ .kind = DITEM_RAW, .val = PROF_RENDER, .proc = DrawProfilerDelta },
|
||||
{ .kind = DITEM_RAW, .val = PROF_ENTITY_LERP, .proc = DrawProfilerDelta },
|
||||
{ .kind = DITEM_END },
|
||||
},
|
||||
//.is_collapsed = 1
|
||||
}
|
||||
},
|
||||
{.kind = DITEM_END},
|
||||
};
|
||||
|
||||
debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool is_shadow) {
|
||||
is_shadow_rendered = is_shadow;
|
||||
for (debug_item *it = list; it->kind != DITEM_END; it += 1) {
|
||||
switch (it->kind) {
|
||||
case DITEM_LIST: {
|
||||
// NOTE(zaklaus): calculate and cache name width for future use
|
||||
if (it->name_width == 0) {
|
||||
it->name_width = UIMeasureText(it->name, DBG_FONT_SIZE);
|
||||
}
|
||||
Color color = RAYWHITE;
|
||||
if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color)) {
|
||||
it->list.is_collapsed = !it->list.is_collapsed;
|
||||
}
|
||||
|
||||
UIDrawText(it->name, xpos, ypos, DBG_FONT_SIZE, color);
|
||||
ypos += DBG_FONT_SPACING;
|
||||
if (it->list.is_collapsed) break;
|
||||
debug_draw_result res = debug_draw_list(it->list.items, xpos+DBG_LIST_XPOS_OFFSET, ypos, is_shadow);
|
||||
ypos = res.y;
|
||||
}break;
|
||||
|
||||
case DITEM_TEXT: {
|
||||
char const *text = TextFormat("%s: ", it->name);
|
||||
if (it->name_width == 0) {
|
||||
it->name_width = UIMeasureText(text, DBG_FONT_SIZE);
|
||||
}
|
||||
UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE);
|
||||
assert(it->proc);
|
||||
|
||||
debug_draw_result res = it->proc(it, xpos + it->name_width, ypos);
|
||||
ypos = res.y;
|
||||
}break;
|
||||
|
||||
case DITEM_RAW: {
|
||||
assert(it->proc);
|
||||
|
||||
debug_draw_result res = it->proc(it, xpos, ypos);
|
||||
ypos = res.y;
|
||||
}break;
|
||||
|
||||
default: {
|
||||
|
||||
}break;
|
||||
}
|
||||
}
|
||||
|
||||
return (debug_draw_result){xpos, ypos};
|
||||
}
|
||||
|
||||
void debug_draw(void) {
|
||||
debug_draw_list(items, DBG_START_XPOS+DBG_SHADOW_OFFSET_XPOS, DBG_START_YPOS+DBG_SHADOW_OFFSET_YPOS, 1); // NOTE(zaklaus): draw shadow
|
||||
debug_draw_list(items, DBG_START_XPOS, DBG_START_YPOS, 0);
|
||||
}
|
||||
|
||||
debug_area_status check_mouse_area(float xpos, float ypos, float w, float h) {
|
||||
if (is_shadow_rendered) return DAREA_OUTSIDE;
|
||||
bool is_inside = CheckCollisionPointRec(GetMousePosition(), (Rectangle){xpos, ypos, w, h});
|
||||
|
||||
if (is_inside) {
|
||||
return IsMouseButtonReleased(MOUSE_LEFT_BUTTON) ? DAREA_PRESS : IsMouseButtonDown(MOUSE_LEFT_BUTTON) ? DAREA_HELD : DAREA_HOVER;
|
||||
}
|
||||
return DAREA_OUTSIDE;
|
||||
}
|
||||
|
||||
bool is_btn_pressed(float xpos, float ypos, float w, float h, Color *color) {
|
||||
assert(color);
|
||||
*color = RAYWHITE;
|
||||
debug_area_status area = check_mouse_area(xpos, ypos, w, h);
|
||||
if (area == DAREA_PRESS) {
|
||||
*color = RED;
|
||||
return true;
|
||||
} else if (area == DAREA_HOVER) {
|
||||
*color = YELLOW;
|
||||
} else if (area == DAREA_HELD) {
|
||||
*color = RED;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline
|
||||
void UIDrawText(const char *text, float posX, float posY, int fontSize, Color color) {
|
||||
// Check if default font has been loaded
|
||||
if (GetFontDefault().texture.id != 0) {
|
||||
Vector2 position = { (float)posX , (float)posY };
|
||||
|
||||
int defaultFontSize = 10; // Default Font chars height in pixel
|
||||
int new_spacing = fontSize/defaultFontSize;
|
||||
|
||||
DrawTextEx(GetFontDefault(), text, position, (float)fontSize , (float)new_spacing , is_shadow_rendered ? BLACK : color);
|
||||
}
|
||||
}
|
||||
|
||||
static inline
|
||||
int UIMeasureText(const char *text, int fontSize) {
|
||||
Vector2 vec = { 0.0f, 0.0f };
|
||||
|
||||
// Check if default font has been loaded
|
||||
if (GetFontDefault().texture.id != 0) {
|
||||
int defaultFontSize = 10; // Default Font chars height in pixel
|
||||
int new_spacing = fontSize/defaultFontSize;
|
||||
|
||||
vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)new_spacing);
|
||||
}
|
||||
|
||||
return (int)vec.x;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#include "debug_ui.h"
|
||||
#include "raylib.h"
|
||||
#include "platform.h"
|
||||
#include "profiler.h"
|
||||
|
||||
//~ NOTE(zaklaus): helpers
|
||||
|
||||
static inline debug_draw_result
|
||||
DrawFloat(float xpos, float ypos, float val) {
|
||||
char const *text = TextFormat("%.02f\n", val);
|
||||
UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE);
|
||||
return (debug_draw_result){.x = xpos + UIMeasureText(text, DBG_FONT_SIZE), .y = ypos + DBG_FONT_SPACING};
|
||||
}
|
||||
|
||||
static inline debug_draw_result
|
||||
DrawFormattedText(float xpos, float ypos, char const *text) {
|
||||
assert(text);
|
||||
UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE);
|
||||
return (debug_draw_result){.x = xpos + UIMeasureText(text, DBG_FONT_SIZE), .y = ypos + DBG_FONT_SPACING};
|
||||
}
|
||||
|
||||
//~ NOTE(zaklaus): widgets
|
||||
|
||||
static inline debug_draw_result
|
||||
DrawDeltaTime(debug_item *it, float xpos, float ypos) {
|
||||
(void)it;
|
||||
float dt = GetFrameTime();
|
||||
return DrawFormattedText(xpos, ypos, TextFormat("%.02f (%.02f fps)", dt * 1000.0f, 1.0f/dt));
|
||||
}
|
||||
|
||||
static inline debug_draw_result
|
||||
DrawZoom(debug_item *it, float xpos, float ypos) {
|
||||
(void)it;
|
||||
|
||||
return DrawFloat(xpos, ypos, platform_zoom_get());
|
||||
}
|
||||
|
||||
static inline debug_draw_result
|
||||
DrawLiteral(debug_item *it, float xpos, float ypos) {
|
||||
assert(it->text);
|
||||
return DrawFormattedText(xpos, ypos, it->text);
|
||||
}
|
||||
|
||||
static inline debug_draw_result
|
||||
DrawProfilerDelta(debug_item *it, float xpos, float ypos) {
|
||||
float dt = profiler_delta(it->val);
|
||||
return DrawFormattedText(xpos, ypos, TextFormat("%s: %.02f ms", profiler_name(it->val), dt * 1000.0f));
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
#include "entity.h"
|
||||
#include "utils/options.h"
|
||||
#include "signal_handling.h"
|
||||
#include "profiler.h"
|
||||
|
||||
#include "flecs/flecs.h"
|
||||
#include "flecs/flecs_dash.h"
|
||||
|
@ -85,9 +86,13 @@ int main(int argc, char** argv)
|
|||
}
|
||||
|
||||
while (game_is_running()) {
|
||||
game_input();
|
||||
game_update();
|
||||
game_render();
|
||||
profile (PROF_MAIN_LOOP) {
|
||||
game_input();
|
||||
game_update();
|
||||
game_render();
|
||||
}
|
||||
|
||||
profiler_collate();
|
||||
}
|
||||
|
||||
game_shutdown();
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "math.h"
|
||||
#include "world/blocks.h"
|
||||
#include "assets.h"
|
||||
#include "profiler.h"
|
||||
#include "debug_ui.h"
|
||||
#include "utils/raylib_helpers.h"
|
||||
|
||||
uint16_t screenWidth = 1600;
|
||||
|
@ -114,8 +116,10 @@ void do_entity_fadeinout(uint64_t key, entity_view * data);
|
|||
float zpl_lerp(float,float,float);
|
||||
|
||||
void platform_render() {
|
||||
game_world_view_active_entity_map(lerp_entity_positions);
|
||||
game_world_view_active_entity_map(do_entity_fadeinout);
|
||||
profile(PROF_ENTITY_LERP) {
|
||||
game_world_view_active_entity_map(lerp_entity_positions);
|
||||
game_world_view_active_entity_map(do_entity_fadeinout);
|
||||
}
|
||||
render_camera.zoom = zpl_lerp(render_camera.zoom, target_zoom, 0.18);
|
||||
camera_update();
|
||||
|
||||
|
@ -124,12 +128,16 @@ void platform_render() {
|
|||
zoom_overlay_tran = zpl_lerp(zoom_overlay_tran, (target_zoom <= CAM_OVERLAY_ZOOM_LEVEL) ? 1.0f : 0.0f, GetFrameTime()*2.0f);
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(GetColor(0x222034));
|
||||
BeginMode2D(render_camera);
|
||||
game_world_view_active_entity_map(DEBUG_draw_ground);
|
||||
game_world_view_active_entity_map(DEBUG_draw_entities);
|
||||
EndMode2D();
|
||||
display_conn_status();
|
||||
profile (PROF_RENDER) {
|
||||
ClearBackground(GetColor(0x222034));
|
||||
BeginMode2D(render_camera);
|
||||
game_world_view_active_entity_map(DEBUG_draw_ground);
|
||||
game_world_view_active_entity_map(DEBUG_draw_entities);
|
||||
EndMode2D();
|
||||
|
||||
display_conn_status();
|
||||
debug_draw();
|
||||
}
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
|
@ -143,9 +151,6 @@ void display_conn_status() {
|
|||
} else {
|
||||
DrawText("Connection: single-player", 5, 5, 12, BLUE);
|
||||
}
|
||||
|
||||
DrawFPS(0, 20);
|
||||
DrawText(TextFormat("Zoom: %.02f", target_zoom), 0, 45, 20, WHITE);
|
||||
}
|
||||
|
||||
void DEBUG_draw_ground(uint64_t key, entity_view * data) {
|
||||
|
@ -262,3 +267,7 @@ void do_entity_fadeinout(uint64_t key, entity_view * data) {
|
|||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
float platform_zoom_get(void) {
|
||||
return target_zoom;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#include "profiler.h"
|
||||
#include "raylib.h"
|
||||
#include <assert.h>
|
||||
|
||||
#define PROF_COLLATE_WINDOW 0.5
|
||||
|
||||
// NOTE(zaklaus): KEEP ORDER IN SYNC WITH profiler_kind ENUM !!!
|
||||
static profiler profilers[] = {
|
||||
{ .id = PROF_MAIN_LOOP, .name = "measured time" },
|
||||
{ .id = PROF_WORLD_WRITE, .name = "world write" },
|
||||
{ .id = PROF_RENDER, .name = "render" },
|
||||
{ .id = PROF_ENTITY_LERP, .name = "entity lerp" },
|
||||
};
|
||||
|
||||
static_assert((sizeof(profilers)/sizeof(profilers[0])) == MAX_PROF, "mismatched profilers");
|
||||
|
||||
void profiler_reset(profiler_kind id) {
|
||||
profilers[id].num_invocations = 0;
|
||||
profilers[id].total_time = 0.0;
|
||||
}
|
||||
|
||||
void profiler_start(profiler_kind id) {
|
||||
profilers[id].start_time = GetTime();
|
||||
}
|
||||
|
||||
void profiler_stop(profiler_kind id) {
|
||||
profilers[id].num_invocations += 1;
|
||||
profilers[id].total_time += GetTime() - profilers[id].start_time;
|
||||
profilers[id].start_time = 0.0;
|
||||
}
|
||||
|
||||
void profiler_collate() {
|
||||
static double frame_counter = 0.0;
|
||||
|
||||
frame_counter += GetFrameTime();
|
||||
|
||||
if (frame_counter >= PROF_COLLATE_WINDOW) {
|
||||
for (uint32_t i = 0; i < MAX_PROF; i += 1) {
|
||||
profiler *p = &profilers[i];
|
||||
p->delta_time = p->num_invocations == 0 ? 0.0 : p->total_time / (double)p->num_invocations;
|
||||
}
|
||||
|
||||
frame_counter = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
double profiler_delta(profiler_kind id) {
|
||||
return profilers[id].delta_time;
|
||||
}
|
||||
|
||||
char const *profiler_name(profiler_kind id) {
|
||||
return profilers[id].name;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
#include "entity_view.h"
|
||||
#include "world/worldgen/worldgen.h"
|
||||
#include "platform.h"
|
||||
#include "profiler.h"
|
||||
|
||||
#include "packets/pkt_send_librg_update.h"
|
||||
|
||||
|
@ -165,34 +166,36 @@ static void world_tracker_update(uint8_t ticker, uint32_t freq, uint8_t radius)
|
|||
if (world.tracker_update[ticker] > zpl_time_rel_ms()) return;
|
||||
world.tracker_update[ticker] = zpl_time_rel_ms() + freq;
|
||||
|
||||
ECS_IMPORT(world.ecs, General);
|
||||
ECS_IMPORT(world.ecs, Net);
|
||||
|
||||
ecs_iter_t it = ecs_query_iter(world.ecs_update);
|
||||
static char buffer[WORLD_LIBRG_BUFSIZ] = {0};
|
||||
world.active_layer_id = ticker;
|
||||
|
||||
while (ecs_query_next(&it)) {
|
||||
ClientInfo *p = ecs_column(&it, ClientInfo, 1);
|
||||
|
||||
for (int i = 0; i < it.count; i++) {
|
||||
size_t datalen = WORLD_LIBRG_BUFSIZ;
|
||||
profile(PROF_WORLD_WRITE) {
|
||||
ECS_IMPORT(world.ecs, General);
|
||||
ECS_IMPORT(world.ecs, Net);
|
||||
|
||||
// TODO(zaklaus): SUPER TEMPORARY HOT !!! simulate variable radius queries
|
||||
{
|
||||
librg_entity_radius_set(world_tracker(), p[i].peer, radius);
|
||||
ecs_iter_t it = ecs_query_iter(world.ecs_update);
|
||||
static char buffer[WORLD_LIBRG_BUFSIZ] = {0};
|
||||
world.active_layer_id = ticker;
|
||||
|
||||
while (ecs_query_next(&it)) {
|
||||
ClientInfo *p = ecs_column(&it, ClientInfo, 1);
|
||||
|
||||
for (int i = 0; i < it.count; i++) {
|
||||
size_t datalen = WORLD_LIBRG_BUFSIZ;
|
||||
|
||||
// TODO(zaklaus): SUPER TEMPORARY HOT !!! simulate variable radius queries
|
||||
{
|
||||
librg_entity_radius_set(world_tracker(), p[i].peer, radius);
|
||||
}
|
||||
|
||||
// TODO(zaklaus): push radius once librg patch comes in
|
||||
int32_t result = librg_world_write(world_tracker(), p[i].peer, buffer, &datalen, NULL);
|
||||
|
||||
if (result > 0) {
|
||||
zpl_printf("[info] buffer size was not enough, please increase it by at least: %d\n", result);
|
||||
} else if (result < 0) {
|
||||
zpl_printf("[error] an error happened writing the world %d\n", result);
|
||||
}
|
||||
|
||||
pkt_send_librg_update((uint64_t)p[i].peer, p[i].view_id, ticker, buffer, datalen);
|
||||
}
|
||||
|
||||
// TODO(zaklaus): push radius once librg patch comes in
|
||||
int32_t result = librg_world_write(world_tracker(), p[i].peer, buffer, &datalen, NULL);
|
||||
|
||||
if (result > 0) {
|
||||
zpl_printf("[info] buffer size was not enough, please increase it by at least: %d\n", result);
|
||||
} else if (result < 0) {
|
||||
zpl_printf("[error] an error happened writing the world %d\n", result);
|
||||
}
|
||||
|
||||
pkt_send_librg_update((uint64_t)p[i].peer, p[i].view_id, ticker, buffer, datalen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue