aboutsummaryrefslogtreecommitdiff
path: root/data/client.cpp
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2026-05-12 22:52:37 +0200
committerPaul Oliver <contact@pauloliver.dev>2026-05-25 04:38:15 +0200
commitbe2c37ac8c8e317eb7e05829ff2078c1b3bbce4e (patch)
tree46caefa23c106ae789bdd49ab59daeca69cb2d3d /data/client.cpp
parent522e11c8086b7d8ab76b9be07c1861f35ed2327f (diff)
Reimplement client with ImGui and ImPlot (scaffold)
Diffstat (limited to 'data/client.cpp')
-rw-r--r--data/client.cpp528
1 files changed, 528 insertions, 0 deletions
diff --git a/data/client.cpp b/data/client.cpp
new file mode 100644
index 0000000..74f2bc5
--- /dev/null
+++ b/data/client.cpp
@@ -0,0 +1,528 @@
+#include <GLFW/glfw3.h>
+#include <imgui.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+#include <implot.h>
+#include <limits.h>
+#include <math.h>
+
+#include <initializer_list>
+
+#include "logger.c"
+
+#define COLOR_BG ImVec4(0.f, 0.f, 0.f, 1.f)
+#define FONT_SIZE 12.f
+#define FONT_SOURCE "/usr/share/fonts/droid/DroidSansMono.ttf"
+#define GLSL_VERSION "#version 130"
+#define WINDOW_STYLE (ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)
+
+#define DEFAULT_ENTRIES 2000
+#define DEFAULT_NTH 1
+#define DEFAULT_X_AXIS 0
+#define DEFAULT_X_LOW 0
+#define DEFAULT_X_HIGH INT_MAX
+#define DEFAULT_HM_LEFT 0
+#define DEFAULT_HM_PIXEL_COUNT 0x400
+
+#define PLOT_MIN_COLS 1
+#define PLOT_MAX_COLS 8
+
+enum Status {
+ STATUS_STOPPED,
+ STATUS_RUNNING,
+ STATUS_STOPPING,
+};
+
+struct Plot {
+ Plot(const char *name, const char *section, std::initializer_list<const char *> cols)
+ : name(name), section(section), cols(cols.begin()) {}
+
+ const char *name;
+ const char *section;
+ const char *const *cols;
+};
+
+// Globals
+GLFWwindow *g_window;
+ImGuiIO *g_io;
+ImGuiStyle *g_imgui_style;
+ImPlotStyle *g_implot_style;
+int g_status;
+
+// Data col
+bool g_data_col_visible = true;
+float g_data_col_width;
+
+const char *g_x_axes[] = {
+ "rowid",
+ "steps",
+#define FOR_CORE(i) "cycl_" #i,
+ FOR_CORES
+#undef FOR_CORE
+};
+
+int g_entries = DEFAULT_ENTRIES;
+int g_nth = DEFAULT_NTH;
+int g_x_axis = DEFAULT_X_AXIS;
+int g_x_low = DEFAULT_X_LOW;
+int g_x_high = DEFAULT_X_HIGH;
+int g_hm_left = DEFAULT_HM_LEFT;
+int g_hm_pixel_count = DEFAULT_HM_PIXEL_COUNT;
+int g_hm_pixel_pow; // calculate on startup
+bool g_data_touched;
+
+// Plots
+Plot g_plots[] = {
+ Plot("cycl", "general", {
+#define FOR_CORE(i) "cycl_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ nullptr,
+ }),
+ Plot("mall", "general", {
+#define FOR_CORE(i) "mall_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ nullptr,
+ }),
+ Plot("pnum", "general", {
+#define FOR_CORE(i) "pnum_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ nullptr,
+ }),
+ Plot("ppop", "general", {
+#define FOR_CORE(i) "pfst_" #i, "plst_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ nullptr,
+ }),
+ Plot("ambs", "general", {
+#define FOR_CORE(i) "amb0_" #i, "amb1_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ nullptr,
+ }),
+ Plot("eevs", "general", {
+#define FOR_CORE(i) "emb0_" #i, "emb1_" #i, "eliv_" #i, "edea_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ nullptr,
+ }),
+};
+
+#define PLOT_COUNT (int)(sizeof(g_plots) / sizeof(g_plots[0]))
+
+// Layout
+int g_plot_cols = 2;
+int g_plot_col_selected;
+int g_plot_row_selected;
+Plot *g_plot_cells[PLOT_MAX_COLS][PLOT_COUNT];
+Plot *g_plot_selected = &g_plots[0];
+bool g_plot_maximized;
+
+// ----------------------------------------------------------------------------
+// Data functions
+// ----------------------------------------------------------------------------
+int data_calc_max_hm_pixel_pow(void) {
+ return (int)floor(log2((float)(MVEC_SIZE - g_hm_left) / (float)g_hm_pixel_count));
+}
+
+void data_clamp(int *field, int low, int high) {
+ assert(field);
+ if (*field < low) *field = low;
+ if (*field > high) *field = high;
+}
+
+void data_validate(void) {
+ data_clamp(&g_entries, 1, DEFAULT_ENTRIES);
+ data_clamp(&g_nth, DEFAULT_NTH, INT_MAX);
+ data_clamp(&g_x_low, DEFAULT_X_LOW, INT_MAX);
+ data_clamp(&g_x_high, g_x_low + 1, DEFAULT_X_HIGH);
+ #if !defined(MVEC_LOOP)
+ data_clamp(&g_hm_left, DEFAULT_HM_LEFT, MVEC_SIZE);
+ #endif
+ data_clamp(&g_hm_pixel_count, 1, DEFAULT_HM_PIXEL_COUNT);
+ data_clamp(&g_hm_pixel_pow, 0, data_calc_max_hm_pixel_pow());
+ g_data_touched = false;
+}
+
+void data_reset_values(void) {
+ g_entries = DEFAULT_ENTRIES;
+ g_nth = DEFAULT_NTH;
+ g_x_axis = DEFAULT_X_AXIS;
+ g_x_low = DEFAULT_X_LOW;
+ g_x_high = DEFAULT_X_HIGH;
+ g_hm_left = DEFAULT_HM_LEFT;
+ g_hm_pixel_count = DEFAULT_HM_PIXEL_COUNT;
+ g_hm_pixel_pow = data_calc_max_hm_pixel_pow();
+}
+
+void data_reset_plot_cells(void) {
+ for (size_t i = 0; i < PLOT_MAX_COLS; i++) {
+ for (size_t j = 0; j < PLOT_COUNT; j++) {
+ g_plot_cells[i][j] = nullptr;
+ }
+ }
+}
+
+void data_start_fetching(void) {
+ assert(g_status == STATUS_RUNNING);
+ log_info("Starting data fetching thread");
+ // start data fetching thread
+}
+
+void data_stop_fetching(void) {
+ assert(g_status == STATUS_STOPPING);
+ log_info("Stopping data fetching thread");
+ // join data fetching thread (set STATUS_STOPPED from within thread)
+ g_status = STATUS_STOPPED;
+}
+
+// ----------------------------------------------------------------------------
+// GUI functions
+// ----------------------------------------------------------------------------
+void gui_print_data_col(void) {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ const ImVec2 next_win_pos = viewport->Pos;
+ const ImVec2 next_win_size = ImVec2(-1.f, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(next_win_pos);
+ ImGui::SetNextWindowSize(next_win_size);
+ ImGui::Begin("data-col", nullptr, WINDOW_STYLE);
+ g_data_col_width = ImGui::GetWindowWidth();
+
+ ImGui::SeparatorText("SALIS data client");
+ ImGui::LabelText("name", NAME);
+ ImGui::LabelText("seed", "%#lx", SEED);
+ ImGui::LabelText("server", IP ":" PORT_STR);
+ ImGui::LabelText("arch", ARCH);
+ ImGui::LabelText("cores", "%d", CORES);
+ ImGui::LabelText("mvec-size", "%#lx", MVEC_SIZE);
+ #if defined(MVEC_LOOP)
+ ImGui::LabelText("mvec-loop", "true");
+ #else
+ ImGui::LabelText("mvec-loop", "false");
+ #endif
+ ImGui::LabelText("data-push", "%#lx", DATA_PUSH_INTERVAL);
+
+ switch (g_status) {
+ case STATUS_STOPPED:
+ ImGui::LabelText("status", "%s", "stopped");
+ break;
+ case STATUS_RUNNING:
+ ImGui::LabelText("status", "%s", "running");
+ break;
+ case STATUS_STOPPING:
+ ImGui::LabelText("status", "%s", "stopping");
+ break;
+ }
+
+ ImGui::SeparatorText("Data fields");
+
+ switch (g_status) {
+ case STATUS_STOPPED:
+ if (ImGui::InputInt("entries", &g_entries, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+ if (ImGui::InputInt("nth", &g_nth, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+
+ if (ImGui::BeginCombo("x-axis", g_x_axes[g_x_axis])) {
+ for (int i = 0; i < CORES + 2; i++) {
+ if (ImGui::Selectable(g_x_axes[i], g_x_axis == i)) {
+ g_x_axis = i;
+ g_data_touched = true;
+ }
+ }
+
+ ImGui::EndCombo();
+ }
+
+ if (ImGui::InputInt("x-low", &g_x_low, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+ if (ImGui::InputInt("x-high", &g_x_high, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+ if (ImGui::InputInt("hm-left", &g_hm_left, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+ if (ImGui::InputInt("hm-pxl-count", &g_hm_pixel_count, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+ if (ImGui::InputInt("hm-pxl-pow", &g_hm_pixel_pow, 0, 0, ImGuiInputTextFlags_CharsDecimal)) g_data_touched = true;
+
+ if (ImGui::Button("Run", ImVec2(-1.f, 0.f))) {
+ g_status = STATUS_RUNNING;
+ data_start_fetching();
+ }
+
+ if (ImGui::Button("Reset", ImVec2(-1.f, 0.f))) data_reset_values();
+
+ break;
+ case STATUS_RUNNING:
+ case STATUS_STOPPING:
+ ImGui::LabelText("entries", "%d", g_entries);
+ ImGui::LabelText("nth", "%d", g_nth);
+ ImGui::LabelText("x-axis", "%s", g_x_axes[g_x_axis]);
+ ImGui::LabelText("x-low", "%d", g_x_low);
+ ImGui::LabelText("x-high", "%d", g_x_high);
+ ImGui::LabelText("hm-left", "%d", g_hm_left);
+ ImGui::LabelText("hm-pxl-count", "%d", g_hm_pixel_count);
+ ImGui::LabelText("hm-pxl-pow", "%d", g_hm_pixel_pow);
+
+ if (g_status == STATUS_RUNNING) {
+ if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) {
+ g_status = STATUS_STOPPING;
+ data_stop_fetching();
+ }
+ }
+ }
+
+ ImGui::SeparatorText("Layout");
+ ImGui::SliderInt("cols", &g_plot_cols, PLOT_MIN_COLS, PLOT_MAX_COLS);
+
+ ImGui::End();
+}
+
+void gui_print_plots(void) {
+ const char *section_current = g_plots[0].section;
+ const char *section_next = nullptr;
+ bool plots_covered[PLOT_COUNT] = { 0 };
+
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ const ImVec2 next_win_pos = g_data_col_visible ? ImVec2(g_data_col_width, viewport->Pos.y) : viewport->Pos;
+ const ImVec2 next_win_size = g_data_col_visible ? ImVec2(viewport->Size.x - g_data_col_width, -1.f) : ImVec2(viewport->Size.x, -1.f);
+
+ ImGui::SetNextWindowPos(next_win_pos);
+ ImGui::SetNextWindowSize(next_win_size);
+ ImGui::Begin("plots", nullptr, WINDOW_STYLE);
+
+ int col = 0;
+ int row = 0;
+
+ while (section_current) {
+ ImGui::SeparatorText(section_current);
+ ImGui::BeginTable("plots-table", g_plot_cols);
+
+ for (int i = 0; i < PLOT_COUNT; i++) {
+ if (g_plots[i].section != section_current) {
+ section_next = (!section_next && !plots_covered[i]) ? g_plots[i].section : section_next;
+ continue;
+ }
+
+ ImGui::TableNextColumn();
+
+ if (&g_plots[i] == g_plot_selected) {
+ g_plot_col_selected = col;
+ g_plot_row_selected = row;
+ g_implot_style->Colors[ImPlotCol_FrameBg] = g_imgui_style->Colors[ImGuiCol_FrameBg];
+ }
+
+ if (ImPlot::BeginPlot(g_plots[i].name)) {
+ int test_x[] = {0,1,2,3};
+ int test_y1[] = {1,2,3,4};
+ int test_y2[] = {2,4,8,16};
+ ImPlot::PlotLine("test1", test_x, test_y1, 4);
+ ImPlot::PlotLine("test2", test_x, test_y2, 4);
+ ImPlot::EndPlot();
+ }
+
+ g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BG;
+
+ g_plot_cells[col][row] = &g_plots[i];
+ col = (col + 1) % g_plot_cols;
+ row += col ? 0 : 1;
+
+ plots_covered[i] = true;
+ }
+
+ section_current = section_next;
+ section_next = nullptr;
+ ImGui::EndTable();
+
+ row += col ? 1 : 0;
+ col = 0;
+ }
+
+ ImGui::End();
+}
+
+void gui_print_plot_maximized(void) {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ ImGui::SetNextWindowPos(viewport->Pos);
+ ImGui::SetNextWindowSize(viewport->Size);
+ ImGui::Begin("plot-fullscreen", nullptr, WINDOW_STYLE);
+
+ if (ImPlot::BeginPlot(g_plot_selected->name, viewport->Size)) {
+ int test_x[] = {0,1,2,3};
+ int test_y1[] = {1,2,3,4};
+ int test_y2[] = {2,4,8,16};
+ ImPlot::PlotLine("test1", test_x, test_y1, 4);
+ ImPlot::PlotLine("test2", test_x, test_y2, 4);
+ ImPlot::EndPlot();
+ }
+
+ ImGui::End();
+}
+
+void gui_print(void) {
+ if (g_plot_maximized) {
+ gui_print_plot_maximized();
+ } else {
+ if (g_data_col_visible) gui_print_data_col();
+ gui_print_plots();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Main functions
+// ----------------------------------------------------------------------------
+void glfw_error_callback(int error, const char* description) {
+ log_warn("GLFW error %d: %s", error, description);
+}
+
+void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ (void)window;
+ (void)scancode;
+
+ if (action != GLFW_PRESS) return;
+
+ if (g_plot_maximized) {
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_C:
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+ break;
+ }
+
+ break;
+
+ case 0:
+ switch (key) {
+ case GLFW_KEY_F:
+ g_plot_maximized = !g_plot_maximized;
+ break;
+ }
+
+ break;
+ }
+
+ return;
+ }
+
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_C:
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+ break;
+ case GLFW_KEY_N:
+ g_data_col_visible = !g_data_col_visible;
+ break;
+ case GLFW_KEY_LEFT:
+ g_plot_cols -= g_plot_cols > 1 ? 1 : 0;
+ data_reset_plot_cells();
+ break;
+ case GLFW_KEY_RIGHT:
+ g_plot_cols += g_plot_cols < PLOT_MAX_COLS ? 1 : 0;
+ data_reset_plot_cells();
+ break;
+ }
+
+ break;
+
+ case 0:
+ switch (key) {
+ case GLFW_KEY_LEFT:
+ g_plot_col_selected -= g_plot_col_selected ? 1 : 0;
+ g_plot_selected = g_plot_cells[g_plot_col_selected][g_plot_row_selected];
+ break;
+ case GLFW_KEY_RIGHT:
+ g_plot_col_selected += (g_plot_col_selected < PLOT_MAX_COLS - 1 && g_plot_cells[g_plot_col_selected + 1][g_plot_row_selected]) ? 1 : 0;
+ g_plot_selected = g_plot_cells[g_plot_col_selected][g_plot_row_selected];
+ break;
+ case GLFW_KEY_UP:
+ g_plot_row_selected -= g_plot_row_selected ? 1 : 0;
+ g_plot_selected = g_plot_cells[g_plot_col_selected][g_plot_row_selected];
+ break;
+ case GLFW_KEY_DOWN:
+ g_plot_row_selected += (g_plot_row_selected < PLOT_COUNT - 1 && g_plot_cells[g_plot_col_selected][g_plot_row_selected + 1]) ? 1 : 0;
+ g_plot_selected = g_plot_cells[g_plot_col_selected][g_plot_row_selected];
+ break;
+ case GLFW_KEY_F:
+ g_plot_maximized = !g_plot_maximized;
+ break;
+ }
+
+ break;
+ }
+}
+
+int main(int argc, char **argv) {
+ (void)argc;
+ (void)argv;
+
+ log_info("Starting SALIS data client");
+
+ log_info("Initializing GLFW");
+ glfwSetErrorCallback(glfw_error_callback);
+ glfwInitHint(GLFW_WAYLAND_LIBDECOR, GLFW_WAYLAND_DISABLE_LIBDECOR);
+ if (!glfwInit()) assert(false);
+
+ float scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor());
+ g_window = glfwCreateWindow((int)(800 * scale), (int)(600 * scale), "SALIS data client", nullptr, nullptr);
+ assert(g_window);
+ glfwSetKeyCallback(g_window, glfw_key_callback);
+ glfwMakeContextCurrent(g_window);
+ glfwSwapInterval(1); // enable vsync
+
+ log_info("Initializing ImGui");
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImPlot::CreateContext();
+
+ g_io = &ImGui::GetIO();
+ g_io->Fonts->AddFontFromFileTTF(FONT_SOURCE);
+ g_io->IniFilename = nullptr;
+
+ g_imgui_style = &ImGui::GetStyle();
+ g_imgui_style->Colors[ImGuiCol_WindowBg] = COLOR_BG;
+ g_imgui_style->FontScaleDpi = scale;
+ g_imgui_style->FontSizeBase = FONT_SIZE;
+ g_imgui_style->ItemSpacing = ImVec2(g_imgui_style->ItemSpacing.x, 2.f);
+ g_imgui_style->ScaleAllSizes(scale);
+
+ g_implot_style = &ImPlot::GetStyle();
+ g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BG;
+
+ ImGui_ImplGlfw_InitForOpenGL(g_window, true);
+ ImGui_ImplOpenGL3_Init(GLSL_VERSION);
+
+ g_hm_pixel_pow = data_calc_max_hm_pixel_pow();
+
+ // Main loop
+ while (!glfwWindowShouldClose(g_window)) {
+ glfwPollEvents();
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ gui_print();
+
+ if (g_data_touched) {
+ data_validate();
+ }
+
+ ImGui::Render();
+ int display_w, display_h;
+ glfwGetFramebufferSize(g_window, &display_w, &display_h);
+ glViewport(0, 0, display_w, display_h);
+ glClearColor(0.f, 0.f, 0.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ glfwSwapBuffers(g_window);
+ }
+
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
+ ImPlot::DestroyContext();
+ ImGui::DestroyContext();
+
+ log_info("Stopping SALIS data client");
+ glfwDestroyWindow(g_window);
+ glfwTerminate();
+ return 0;
+}