Skip to content

Instantly share code, notes, and snippets.

@KotBasilio
Last active January 23, 2026 12:21
Show Gist options
  • Select an option

  • Save KotBasilio/144f4f6c9e554da87c8ca4c57beec67e to your computer and use it in GitHub Desktop.

Select an option

Save KotBasilio/144f4f6c9e554da87c8ca4c57beec67e to your computer and use it in GitHub Desktop.
for Bob And Trace
#include "graph_types.h"
static ImVec2 AnchorRightMid(const ImVec2& node_min, const ImVec2& node_size)
{
return ImVec2(node_min.x + node_size.x, node_min.y + node_size.y * 0.5f);
}
static ImVec2 AnchorLeftMid(const ImVec2& node_min, const ImVec2& node_size)
{
return ImVec2(node_min.x, node_min.y + node_size.y * 0.5f);
}
static ImVec2 AnchorTopMid(const ImVec2& p, const ImVec2& s)
{
return ImVec2(p.x + s.x * 0.5f, p.y);
}
static ImVec2 AnchorBottomMid(const ImVec2& p, const ImVec2& s)
{
return ImVec2(p.x + s.x * 0.5f, p.y + s.y);
}
ImVec2 GraphLink::AnchorForPort(GraphPort port, const ImVec2 &p, const ImVec2& s) const
{
switch (port) {
case GraphPort::Left: return AnchorLeftMid(p, s);
case GraphPort::Right: return AnchorRightMid(p, s);
case GraphPort::Top: return AnchorTopMid(p, s);
case GraphPort::Bottom: return AnchorBottomMid(p, s);
default:
// Default graph convention: left -> right flow
return AnchorRightMid(p, s);
}
}
ImVec2 GraphLink::AnchorFrom(const ImVec2& p, const ImVec2& s) const
{
return AnchorForPort(fromPort, p, s);
}
ImVec2 GraphLink::AnchorTo(const ImVec2& p, const ImVec2& s) const
{
return AnchorForPort(toPort, p, s);
}
// graph_types.h
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <imgui.h>
using NodeId = uint64_t;
enum class NodeKind : uint8_t
{
Unknown,
DSSession,
HeatedDSServer,
SCSession,
MMFlowSample,
User,
Party,
MMSession,
StandaloneServer,
HydraSample,
// add as needed
};
enum class GraphPort : uint8_t {
Left,
Right,
Top,
Bottom,
};
struct GraphNode
{
NodeId id = 0;
NodeKind kind = NodeKind::Unknown;
std::string title; // e.g. "ServUser0050"
std::string subtitle; // e.g. "User" or short id
ImVec2 pos; // in "graph space" coordinates (not screen)
ImVec2 size; // node box size in graph space
// Visual/style tags (optional but handy)
uint32_t colorBorder = 0; // IM_COL32(...)
uint32_t colorFill = 0;
uint32_t colorText = 0;
// Inspector payload key (points to some domain entity id)
std::string entityKey; // e.g. userId/sessionId etc.
};
enum class LinkStyle : uint8_t
{
Straight,
Bezier,
};
struct GraphLink
{
NodeId from = 0;
NodeId to = 0;
GraphPort fromPort = GraphPort::Right;
GraphPort toPort = GraphPort::Left;
LinkStyle style = LinkStyle::Bezier;
// Optional visuals
bool arrow = true;
bool dotted = false;
uint32_t color = IM_COL32(180, 180, 180, 255);
ImVec2 AnchorFrom(const ImVec2& p, const ImVec2& s) const;
ImVec2 AnchorTo (const ImVec2& p, const ImVec2& s) const;
private:
ImVec2 AnchorForPort(GraphPort port, const ImVec2& p, const ImVec2& s) const;
};
struct GraphModel
{
std::vector<GraphNode> nodes;
std::vector<GraphLink> links;
// Quick lookup (build once after changes)
std::unordered_map<NodeId, size_t> indexById;
void RebuildIndex()
{
indexById.clear();
indexById.reserve(nodes.size());
for (size_t i = 0; i < nodes.size(); ++i)
indexById[nodes[i].id] = i;
}
GraphNode* Find(NodeId id)
{
auto it = indexById.find(id);
return (it == indexById.end()) ? nullptr : &nodes[it->second];
}
};
// Shared selection spine across panels (Inventory, Graph, Inspector).
struct SelectionState {
NodeId node = 0; // 0 = nothing selected
};
// Minimal view state for a canvas: pan now, zoom later.
struct GraphViewState {
ImVec2 pan = ImVec2(0.0f, 0.0f); // pixels
float zoom = 1.0f;
SelectionState selected{}; // selection spine
};
#include "ui/controllers/main_controller.h"
#include "ui/models/main_model.h"
#include "ui/views/main_window.h"
#include "ui/controllers/start_server_controller.h"
#include <chrono>
#pragma message("main_controller.cpp REV: Path A v0.6")
namespace Sample::UI::Controllers {
MainController::MainController(Sample::Connector::BackendConnectorInterface* connector, SampleUIInterface* sample_ui)
: backendConnector(connector)
, window(std::make_unique<Views::MainWindow>(&mainModel, this))
, serverController(std::make_unique<StartServerController>(&mainModel, connector))
, sampleUI(sample_ui)
{
StartServer();
}
MainController::~MainController()
{
StopServer();
}
void MainController::ShowWindow()
{
window->Show();
}
void MainController::ShowLogsWindow()
{
sampleUI->ShowLogsWindow(&mainModel.logsCheck);
}
void MainController::ShowLoginWindow()
{
sampleUI->ShowLoginWindow(&mainModel.loginCheck);
}
void MainController::SetMessageWindowActive()
{
mainModel.messagesCheck = true;
}
void MainController::SetUserName(const std::string& userName)
{
mainModel.userName = userName;
}
void MainController::SetAppState(const UI::Models::State& state)
{
mainModel.appState = state;
}
void MainController::StartServer()
{
serverController->StartServer(UI::Models::ServerType::LOGS_PARSER);
}
void MainController::TickIngestion()
{
serverController->TickIngestion();
}
void MainController::StopServer()
{
serverController->StopServer();
}
} // namespace Sample::UI::Controllers
#pragma once
#include "ui/views/window_interface.h"
#include "ui/models/main_model.h"
#include "ui/controllers/logs_controller.h"
#include "ui/controllers/login_controller.h"
#include "ui/controllers/start_server_controller.h"
#include "connectors/backend_connector.h"
#include "main_controller_interface.h"
#include "connectors/ui_connector.h"
#include "sample_ui_interface.h"
#pragma message("main_controller.h REV: Path A v0.6")
namespace Sample::UI::Controllers {
class MainController : public MainControllerInterface {
public:
MainController(Sample::Connector::BackendConnectorInterface* connector, SampleUIInterface* sample_ui);
~MainController();
void ShowWindow() override;
void ShowLogsWindow() override;
void ShowLoginWindow() override;
void StartServer() override;
void TickIngestion() override;
void StopServer() override;
void SetUserName(const std::string& userName) override;
void SetMessageWindowActive() override;
void SetAppState(const UI::Models::State& state) override;
private:
Sample::Connector::BackendConnectorInterface* backendConnector;
Sample::UI::Models::MainModel mainModel;
std::unique_ptr<UI::Views::WindowInterface> window;
std::unique_ptr<UI::Controllers::StartServerControllerInterface> serverController;
SampleUIInterface* sampleUI;
};
} // namespace Sample::UI::Controllers
#pragma once
#include "ui/views/window_interface.h"
#include "ui/models/main_model.h"
#include "ui/controllers/logs_controller.h"
#include "ui/controllers/login_controller.h"
#include "ui/controllers/start_server_controller.h"
#include "connectors/backend_connector.h"
#include "main_controller_interface.h"
#include "connectors/ui_connector.h"
#include "sample_ui_interface.h"
#pragma message("main_controller.h REV: Path A v0.6")
namespace Sample::UI::Controllers {
class MainController : public MainControllerInterface {
public:
MainController(Sample::Connector::BackendConnectorInterface* connector, SampleUIInterface* sample_ui);
~MainController();
void ShowWindow() override;
void ShowLogsWindow() override;
void ShowLoginWindow() override;
void StartServer() override;
void TickIngestion() override;
void StopServer() override;
void SetUserName(const std::string& userName) override;
void SetMessageWindowActive() override;
void SetAppState(const UI::Models::State& state) override;
private:
Sample::Connector::BackendConnectorInterface* backendConnector;
Sample::UI::Models::MainModel mainModel;
std::unique_ptr<UI::Views::WindowInterface> window;
std::unique_ptr<UI::Controllers::StartServerControllerInterface> serverController;
SampleUIInterface* sampleUI;
};
} // namespace Sample::UI::Controllers
#pragma once
#include <string>
#include <deque>
#include <cstdint>
#include <cstddef>
#include "ui/utils/graph_types.h"
#pragma message("main_model.h REV: Path A v0.6")
namespace Sample::UI::Models {
enum class State {
DEFAULT,
USER_LOGGING,
SERVER_STARTING,
USER_LOGGED,
SERVER_STARTED
};
enum class LogLevel : uint8_t {
Info,
Warn,
Error
};
struct LogEntry {
uint32_t seq = 0;
LogLevel level = LogLevel::Info;
double time_s = 0.0; // caller-provided timestamp (e.g. ImGui::GetTime())
std::string text;
};
struct LogBuffer {
std::deque<LogEntry> entries;
uint32_t nextSeq = 1;
size_t maxEntries = 5000;
void Push(LogLevel lvl, std::string msg, double time_s = 0.0);
void Clear() { entries.clear(); }
};
struct MainModel {
~MainModel();
State appState = State::DEFAULT;
bool loginCheck = false;
bool serverStartCheck = false;
bool friendsCheck = false;
bool logsCheck = false;
bool messagesCheck = false;
bool profileCheck = false;
bool partyLobbyCheck = false;
bool searchSessionCheck = false;
bool serverSearchSessionCheck = false;
bool sessionControlCheck = false;
bool matchmakingCheck = false;
bool qrFlowCheck = false;
bool gameConfigCheck = false;
bool hydraOssCheck = false;
bool lyraCheck = false;
bool graphUsesIngestion = false;
std::string userName;
LogBuffer logs;
::GraphModel graph;
};
} // namespace Sample::UI::Models
#include "main_window.h"
#include "imgui_internal.h" // DockBuilder* live here
#include "ui/models/main_model.h"
#include "ui/utils/texture_manager.h"
#pragma message("main_window.cpp REV: Path A v0.6")
namespace Sample::UI::Views {
MainWindow::MainWindow(Sample::UI::Models::MainModel* model, Sample::UI::Controllers::MainControllerInterface* controller)
: mainModel(model)
, mainController(controller)
, gGraphPanel(gGraphView, model->graph, gTex)
, gInspectorPanel(gGraphView, model->graph, gTex)
, gUnitsPanel(gGraphView, model->graph, gTex)
, gLogsPanel(mainModel->logs)
{
//gTex.Prefetch(".\\Assets\\AssetList.txt");
gTex.Prefetch();
}
void MainWindow::Show()
{
switch (mainModel->appState) {
case Models::State::DEFAULT:
{
ShowDefaultWindow();
break;
}
case Models::State::USER_LOGGING:
{
break;
}
case Models::State::SERVER_STARTING:
{
break;
}
case Models::State::USER_LOGGED:
{
break;
}
case Models::State::SERVER_STARTED:
{
break;
}
default:
{
}
}
}
static void BuildDefaultDockLayout(ImGuiID dockspace_id)
{
// If already built, don't rebuild (preserve user layout from imgui.ini)
if (ImGui::DockBuilderGetNode(dockspace_id) != nullptr)
return;
ImGuiViewport* vp = ImGui::GetMainViewport();
ImGui::DockBuilderRemoveNode(dockspace_id);
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockspace_id, vp->WorkSize);
// Split: left | center | right
ImGuiID dock_main = dockspace_id;
ImGuiID dock_left = 0;
ImGuiID dock_right = 0;
ImGuiID dock_center = dock_main; // dock_center is now the top region (Graph)
// Left 20%
dock_left = ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Left, 0.20f, nullptr, &dock_center);
// Right 25% (of remaining)
dock_right = ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Right, 0.25f, nullptr, &dock_center);
// Center split: top (Graph) and bottom (Logs)
ImGuiID dock_logs = 0;
dock_logs = ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Down, 0.30f, nullptr, &dock_center);
// Dock windows (names must match ImGui::Begin titles exactly)
ImGui::DockBuilderDockWindow("Units", dock_left);
ImGui::DockBuilderDockWindow("Inspector", dock_right);
ImGui::DockBuilderDockWindow("Graph", dock_center);
ImGui::DockBuilderDockWindow("Logs", dock_logs);
ImGui::DockBuilderFinish(dockspace_id);
}
void MainWindow::RefreshDockingSetup()
{
// Fullscreen host window over the main viewport
ImGuiViewport* vp = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(vp->WorkPos);
ImGui::SetNextWindowSize(vp->WorkSize);
ImGui::SetNextWindowViewport(vp->ID);
ImGuiWindowFlags host_flags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_MenuBar;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("##DockHost", nullptr, host_flags);
ImGui::PopStyleVar(3);
// DockSpace
const ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
ImGuiDockNodeFlags dock_flags = ImGuiDockNodeFlags_None;
// Prevent docking into the central node (common for "canvas" apps)
dock_flags |= ImGuiDockNodeFlags_NoDockingInCentralNode;
ImGui::DockSpace(dockspace_id, ImVec2(0, 0), dock_flags);
// Build default layout (only if no node exists yet)
BuildDefaultDockLayout(dockspace_id);
// docked menu
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("Start")) {
ImGui::MenuItem("Matchmaking", nullptr, &mainModel->matchmakingCheck);
ImGui::MenuItem("QR flow", nullptr, &mainModel->qrFlowCheck);
ImGui::MenuItem("Game Configuration", nullptr, &mainModel->gameConfigCheck);
ImGui::Separator();
ImGui::MenuItem("Hydra OSS", nullptr, &mainModel->hydraOssCheck);
ImGui::MenuItem("Lyra", nullptr, &mainModel->lyraCheck);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::End();
}
void Sample::UI::Views::MainWindow::RenderAllPanes()
{
// Dev switch
#ifdef MINIMAL_PANES
ImGui::Begin("Units"); ImGui::Text("Units / navigation go here"); ImGui::End();
ImGui::Begin("Graph"); ImGui::Text("Flow visualization canvas"); ImGui::End();
ImGui::Begin("Inspector"); ImGui::Text("Selected node details"); ImGui::End();
ImGui::Begin("Logs"); ImGui::Text("Runtime logs"); ImGui::End();
return;
#endif // MINIMAL_PANES
// Draw four panels normally (they'll dock automatically)
// Left: Units
if (ImGui::Begin("Units")) {
gUnitsPanel.Draw();
ImGui::End();
}
// Right: Inspector
if (ImGui::Begin("Inspector")) {
gInspectorPanel.Draw();
ImGui::End();
}
// Center top: Graph
if (ImGui::Begin("Graph")) {
gGraphPanel.Draw();
ImGui::End();
}
// Center bottom: Logs
if (ImGui::Begin("Logs")) {
gLogsPanel.Draw();
ImGui::End();
}
}
void MainWindow::ConsiderLaunchingSample()
{
//if (mainModel->matchmakingCheck)
// mainController->ShowMatchmakingWindow();
//if (mainModel->qrFlowCheck)
// mainController->ShowQRFlowWindow();
//if (mainModel->gameConfigCheck)
// mainController->ShowGameConfigWindow();
//if (mainModel->hydraOssCheck)
// mainController->ShowHydraOSSWindow();
//if (mainModel->lyraCheck)
// mainController->ShowLyraWindow();
}
void MainWindow::ShowDefaultWindow()
{
RefreshDockingSetup();
RenderAllPanes();
ConsiderLaunchingSample();
}
} // namespace Sample::UI::Views
#pragma once
#include "ui/models/main_model.h"
#include "ui/controllers/main_controller_interface.h"
#include "ui/utils/graph_panel.h"
#include "ui/utils/inspector_panel.h"
#include "ui/utils/units_panel.h"
#include "ui/utils/logs_panel.h"
#include "window_interface.h"
#pragma message("main_window.h REV: Path A v0.6")
namespace Sample::UI::Views {
using namespace Sample::Tex;
class MainWindow : public WindowInterface {
public:
MainWindow(Models::MainModel* model, Controllers::MainControllerInterface* controller);
void Show() override;
private:
void ShowDefaultWindow();
void ConsiderLaunchingSample();
void RenderAllPanes();
void RefreshDockingSetup();
private:
Sample::UI::Models::MainModel* mainModel;
Sample::UI::Controllers::MainControllerInterface* mainController;
GraphViewState gGraphView;
TextureManager gTex;
GraphPanel gGraphPanel;
InspectorPanel gInspectorPanel;
UnitsPanel gUnitsPanel;
LogsPanel gLogsPanel;
};
} // namespace Sample::UI::Views
#include "ui/controllers/start_server_controller.h"
#include "ui/models/start_server_model.h"
#pragma message("start_server_controller.cpp REV: Graph nodes v0.1")
static void HttplibJsonSmokeTest()
{
httplib::Server svr;
nlohmann::json j = { {"hello", "world"} };
(void)svr; (void)j;
}
static double NowSeconds()
{
using clock = std::chrono::steady_clock;
static const auto t0 = clock::now();
auto dt = std::chrono::duration<double>(clock::now() - t0);
return dt.count();
}
namespace Sample::UI::Controllers {
CoordinatorHttpServer::CoordinatorHttpServer(Models::MainModel* model)
{
mainModel = model;
}
void CoordinatorHttpServer::Start(const char* host, int port)
{
if (running_.exchange(true)) return; // already running
thr_ = std::thread([this, host_s = std::string(host), port]() {
ServerThreadMain(host_s, port);
});
}
void CoordinatorHttpServer::Stop()
{
if (!running_.exchange(false)) return;
// Stop server (thread-safe in httplib)
if (svr_) svr_->stop();
if (thr_.joinable())
thr_.join();
}
bool CoordinatorHttpServer::TryPop(CoordinatorUpdate& out)
{
std::lock_guard<std::mutex> lock(mtx_);
if (queue_.empty()) return false;
out = std::move(queue_.front());
queue_.pop_front();
return true;
}
void CoordinatorHttpServer::ServerThreadMain(std::string host, int port)
{
httplib::Server svr;
svr_ = &svr;
if (!mainModel) {
return;
}
auto handler = [this](const httplib::Request& req, httplib::Response& res) {
try {
auto I = [&](const char* s) { mainModel->logs.Push(UI::Models::LogLevel::Info, std::string(s)); };
auto j = nlohmann::json::parse(req.body);
CoordinatorUpdate u;
u.payload = std::move(j);
u.recv_time_s = NowSeconds();
// Pretty-print for display; keep it bounded so logs don't explode
u.printable = u.payload.dump(2); // indent=2
constexpr size_t kMaxLogChars = 4000;
if (u.printable.size() > kMaxLogChars) {
u.printable.resize(kMaxLogChars);
u.printable += "\n...<truncated>...";
}
I("Received coordinator packet with payload");
mainModel->logs.Push(UI::Models::LogLevel::Info, u.printable, u.recv_time_s);
{
std::lock_guard<std::mutex> lock(mtx_);
queue_.push_back(std::move(u));
if (queue_.size() > 2000) queue_.pop_front();
}
res.status = 200;
res.set_content("OK\n", "text/plain");
}
catch (const std::exception& e) {
res.status = 400;
res.set_content(std::string("Bad JSON: ") + e.what() + "\n", "text/plain");
}
};
// Simple endpoint: POST '/coordinator' with JSON body
svr.Post("/coordinator", handler);
// A tiny health check helps debugging
svr.Get("/health", [this](const httplib::Request&, httplib::Response& res) {
auto I = [&](const char* s) { mainModel->logs.Push(UI::Models::LogLevel::Info, std::string(s)); };
res.status = 200;
res.set_content("alive\n", "text/plain");
I("Health check");
});
// This blocks until stop() is called
svr.listen(host.c_str(), port);
svr_ = nullptr;
}
StartServerController::StartServerController(Models::MainModel* model, Sample::Connector::BackendConnectorInterface* connector)
: backendConnector(connector)
, server(model)
, mainModel(model)
{
//HttplibJsonSmokeTest();
}
bool StartServerController::StartServer(const Models::ServerType serverType)
{
server.Start();
return true;
}
void StartServerController::StopServer()
{
server.Stop();
}
void StartServerController::SetStartServerServiceState(const UI::Models::ServiceState& serviceState)
{
startServerModel.serviceState = serviceState;
}
UI::Models::ServiceState StartServerController::GetServiceState() const
{
return startServerModel.serviceState;
}
void StartServerController::TickIngestion()
{
if (!mainModel) {
return;
}
CoordinatorUpdate u;
bool any = false;
while (server.TryPop(u)) {
any = true;
auto& logs = mainModel->logs;
auto& graph = mainModel->graph;
const auto& p = u.payload;
if (!p.is_object()) {
logs.Push(Models::LogLevel::Warn, "coordinator: payload is not a JSON object");
continue;
}
// First packet: switch graph to ingestion-driven (wipe any demo seed)
if (!mainModel->graphUsesIngestion) {
mainModel->graphUsesIngestion = true;
graph.nodes.clear();
graph.links.clear();
graph.indexById.clear();
// If you can reach view.selected here later, clear it too; for now this prevents stale finds.
logs.Push(Models::LogLevel::Info, "coordinator: first packet -> switching graph to ingestion-driven");
}
// Stable ids for the slice
constexpr NodeId AUTH_ID = 1001;
constexpr NodeId ACCOUNT_ID = 1002;
// authState
auto itA = p.find("authState");
if (itA != p.end() && itA->is_object()) {
const auto& a = *itA;
const std::string serviceState = GetStr(a, "serviceState", "<unknown>");
const std::string id = GetStr(a, "id", "<no-id>");
const std::string provider = GetStr(a, "provider", "<unknown>");
const std::string platform = GetStr(a, "platform", "<unknown>");
logs.Push(Models::LogLevel::Info,
"coordinator: authState State=" + serviceState +
" Id=" + id + " Provider=" + provider + " Platform=" + platform);
auto& n = UpsertNode(graph, AUTH_ID);
n.kind = NodeKind::Unknown;
n.title = "Auth";
n.subtitle = serviceState + " | " + id;
n.entityKey = "auth";
n.pos = ImVec2(0, 0);
n.size = ImVec2(160, 70);
}
// accountStatus
auto itS = p.find("accountStatus");
if (itS != p.end() && itS->is_object()) {
const auto& s = *itS;
const std::string linkState = GetStr(s, "linkState", "<unknown>");
const std::string systemMode = GetStr(s, "systemMode", "<unknown>");
logs.Push(Models::LogLevel::Info,
"coordinator: accountStatus LinkState=" + linkState +
" SystemMode=" + systemMode);
auto& n = UpsertNode(graph, ACCOUNT_ID);
n.kind = NodeKind::Unknown;
n.title = "Account";
n.subtitle = linkState + " | " + systemMode;
n.entityKey = "account";
n.pos = ImVec2(240, 0);
n.size = ImVec2(200, 70);
}
// Only add link if both nodes exist in the model
//if (FindNodeLinear(graph, AUTH_ID) && FindNodeLinear(graph, ACCOUNT_ID))
// EnsureLink(graph, AUTH_ID, ACCOUNT_ID);
}
if (any)
mainModel->graph.RebuildIndex();
}
} // namespace Sample::UI::Controllers
#pragma once
#include <memory>
#include <vector>
#include <mutex>
#include <deque>
#include <string>
#include <optional>
#include <thread>
#include <atomic>
#include "httplib.h"
#include "json.hpp"
#include "ui/models/start_server_model.h"
#include "connectors/backend_connector.h"
#include "start_server_controller_interface.h"
#include "ui/views/window_interface.h"
#pragma message("start_server_controller.h REV: Graph nodes v0.1")
namespace Sample::UI::Controllers {
struct CoordinatorUpdate {
nlohmann::json payload;
std::string printable; // for logs/debug display
double recv_time_s = 0.0;
};
class CoordinatorHttpServer {
public:
CoordinatorHttpServer(Models::MainModel* model);
~CoordinatorHttpServer() { Stop(); }
void Start(const char* host = "127.0.0.1", int port = 30001);
void Stop();
// Called from UI thread (TickIngestion) to drain events
bool TryPop(CoordinatorUpdate& out);
private:
void ServerThreadMain(std::string host, int port);
private:
std::mutex mtx_;
std::deque<CoordinatorUpdate> queue_;
std::thread thr_;
std::atomic<bool> running_{ false };
// Owned by server thread
httplib::Server* svr_ = nullptr;
Models::MainModel* mainModel = nullptr;
};
class StartServerController : public StartServerControllerInterface {
public:
StartServerController(Models::MainModel* model, Sample::Connector::BackendConnectorInterface* connector);
bool StartServer(const Models::ServerType serverType) override;
void TickIngestion() override;
void StopServer() override;
void SetStartServerServiceState(const UI::Models::ServiceState& serviceState) override;
UI::Models::ServiceState GetServiceState() const override;
private:
Sample::Connector::BackendConnectorInterface* backendConnector;
Models::StartServerModel startServerModel;
Models::MainModel* mainModel = nullptr;
CoordinatorHttpServer server;
};
} // namespace Sample::UI::Controllers
#include "graph_panel.h"
#pragma message("stups.cpp REV: Graph nodes v0.1")
void GraphPanel::EnsureModelPresence()
{
if (!model.nodes.empty()) {
return;
}
// Demo nodes if model empty
model.links.clear();
// Layout: 3 columns-ish, left->right flow
const ImVec2 s32{ 32, 32 };
// IDs
const NodeId N_USER0 = 1;
const NodeId N_USER1 = 11;
const NodeId N_USER2 = 13;
const NodeId N_PARTY = 2;
const NodeId N_SCSESS = 3;
const NodeId N_DSSESS = 4;
const NodeId N_MMFLOW = 5;
const NodeId N_MMSESS = 6;
const NodeId N_HEATED = 7;
const NodeId N_STANDALONE = 8;
const NodeId N_HYDRA = 9;
const NodeId N_UNKNOWN = 10;
const NodeId N_INFO2 = 12;
// Column X positions
const float x0 = 80.0f;
const float x1 = 260.0f;
const float x2 = 440.0f;
const float x3 = 620.0f;
const float x2H = 0.5f * (x2 + x3);
const float x4 = 660.0f;
// Row Y positions
const float yS = 25.0f;
const float yZ = yS + 20.0f;
const float y0 = yS + 80.0f;
const float yH = yS + 160.0f;
const float y1 = yS + 240.0f;
const float y2 = yS + 320.0f;
const float y3 = yS + 380.0f;
// Nodes: icon + title
model.nodes.push_back(GraphNode{ N_USER0, NodeKind::User, "User0", "BroUser50", ImVec2{x0, yZ}, s32 });
model.nodes.push_back(GraphNode{ N_USER1, NodeKind::User, "User1", "ServUser50", ImVec2{x0, y0}, s32 });
model.nodes.push_back(GraphNode{ N_USER2, NodeKind::User, "User2", "titleU", ImVec2{x0, yH}, s32 });
model.nodes.push_back(GraphNode{ N_PARTY, NodeKind::Party, "Party", "Party#12", ImVec2{x0, y1}, s32 });
model.nodes.push_back(GraphNode{ N_SCSESS, NodeKind::SCSession, "SC Session", "SCSession#3", ImVec2{x0, y2}, s32 });
model.nodes.push_back(GraphNode{ N_INFO2, NodeKind::Unknown, "Info2", "titleH", ImVec2{x3, y3}, s32 });
model.nodes.push_back(GraphNode{ N_DSSESS, NodeKind::DSSession, "DS Session", "DSSession#7", ImVec2{x1, y0}, s32 });
model.nodes.push_back(GraphNode{ N_MMFLOW, NodeKind::MMFlowSample, "MM Flow", "FlowSample", ImVec2{x1, y1}, s32 });
model.nodes.push_back(GraphNode{ N_MMSESS, NodeKind::MMSession, "MM Session", "MMSession#2", ImVec2{x1, y2}, s32 });
model.nodes.push_back(GraphNode{ N_HEATED, NodeKind::HeatedDSServer, "Heated DS", "Server A", ImVec2{x2, yH}, s32 });
model.nodes.push_back(GraphNode{ N_STANDALONE, NodeKind::StandaloneServer, "Standalone", "Server B", ImVec2{x2, y2}, s32 });
model.nodes.push_back(GraphNode{ N_HYDRA, NodeKind::HydraSample, "Hydra", "OSS", ImVec2{x3, y1}, s32 });
model.nodes.push_back(GraphNode{ N_UNKNOWN, NodeKind::Unknown, "Info1", "?", ImVec2{x2, yZ}, s32 });
// 10 links, generally left->right
model.links.push_back(GraphLink{ N_USER0, N_DSSESS }); // 1
model.links.push_back(GraphLink{ N_PARTY, N_MMFLOW }); // 2
model.links.push_back(GraphLink{ N_SCSESS, N_MMSESS }); // 3
model.links.push_back(GraphLink{ N_DSSESS, N_HEATED }); // 4
model.links.push_back(GraphLink{ N_MMFLOW, N_HEATED }); // 5
model.links.push_back(GraphLink{ N_MMSESS, N_STANDALONE }); // 6
model.links.push_back(GraphLink{ N_HEATED, N_HYDRA }); // 7
model.links.push_back(GraphLink{ N_STANDALONE, N_HYDRA }); // 8
model.links.push_back(GraphLink{ N_DSSESS, N_UNKNOWN }); // 9
model.links.push_back(GraphLink{ N_UNKNOWN, N_HYDRA }); // 10
model.links.push_back(GraphLink{ N_USER1, N_DSSESS });
model.links.push_back(GraphLink{ N_USER2, N_DSSESS });
model.links.push_back(GraphLink{ N_STANDALONE, N_INFO2 });
model.RebuildIndex();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment