Last active
January 23, 2026 12:21
-
-
Save KotBasilio/144f4f6c9e554da87c8ca4c57beec67e to your computer and use it in GitHub Desktop.
for Bob And Trace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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); | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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 | |
| }; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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