deargui-vpl/examples/blueprints-example/blueprints-example.cpp

394 lines
13 KiB
C++

#include "app.h"
#include "core/graph_state.h"
#include "blocks/block.h"
#include "blocks/start_block.h"
#include "containers/root_container.h"
#include "crude_json.h"
#include <imgui.h>
#include <map>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
#include <chrono>
#include "Logging.h"
#if defined(_WIN32)
#include <windows.h>
#include <psapi.h>
#pragma comment(lib, "Psapi.lib")
#define PRINT_STATS 0
struct MemoryUsageWin32
{
SIZE_T workingSet = 0;
SIZE_T privateBytes = 0;
};
struct CpuTimesWin32
{
ULONGLONG kernel = 0;
ULONGLONG user = 0;
};
static MemoryUsageWin32 CaptureMemoryUsageWin32()
{
MemoryUsageWin32 usage;
PROCESS_MEMORY_COUNTERS_EX pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS *>(&pmc), sizeof(pmc)))
{
usage.workingSet = pmc.WorkingSetSize;
usage.privateBytes = pmc.PrivateUsage;
}
return usage;
}
static void LogMemoryUsageWin32(const char *label, const MemoryUsageWin32 &usage)
{
double workingSetMb = static_cast<double>(usage.workingSet) / (1024.0 * 1024.0);
double privateMb = static_cast<double>(usage.privateBytes) / (1024.0 * 1024.0);
LOG_INFO("[Memory] {} - Working Set: {:.2f} MB, Private Bytes: {:.2f} MB", label, workingSetMb, privateMb);
}
static CpuTimesWin32 CaptureCpuTimesWin32()
{
CpuTimesWin32 result;
FILETIME creation{}, exit{}, kernel{}, user{};
if (GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user))
{
ULARGE_INTEGER k{};
k.LowPart = kernel.dwLowDateTime;
k.HighPart = kernel.dwHighDateTime;
ULARGE_INTEGER u{};
u.LowPart = user.dwLowDateTime;
u.HighPart = user.dwHighDateTime;
result.kernel = k.QuadPart;
result.user = u.QuadPart;
}
return result;
}
static double ComputeCpuUsagePercentWin32(const CpuTimesWin32 &begin,
const CpuTimesWin32 &end,
double elapsedSeconds)
{
if (elapsedSeconds <= 0.0)
return 0.0;
ULONGLONG deltaKernel = end.kernel - begin.kernel;
ULONGLONG deltaUser = end.user - begin.user;
double cpuSeconds = static_cast<double>(deltaKernel + deltaUser) / 10'000'000.0;
SYSTEM_INFO info;
GetSystemInfo(&info);
double cores = static_cast<double>(info.dwNumberOfProcessors > 0 ? info.dwNumberOfProcessors : 1);
double usage = (cpuSeconds / (elapsedSeconds * cores)) * 100.0;
if (usage < 0.0)
usage = 0.0;
return usage;
}
#endif
// Helper to get string argument with default
std::string GetStringArg(const ArgsMap &args, const std::string &key, const std::string &defaultValue = "")
{
auto it = args.find(key);
if (it != args.end() && it->second.Type == ArgValue::Type::String)
return it->second.String;
return defaultValue;
}
// Helper to get bool argument with default
bool GetBoolArg(const ArgsMap &args, const std::string &key, bool defaultValue = false)
{
auto it = args.find(key);
if (it != args.end() && it->second.Type == ArgValue::Type::Bool)
return it->second.Bool;
return defaultValue;
}
int GetIntArg(const ArgsMap &args, const std::string &key, int defaultValue = 0)
{
auto it = args.find(key);
if (it == args.end())
return defaultValue;
if (it->second.Type == ArgValue::Type::Int)
return static_cast<int>(it->second.Int);
if (it->second.Type == ArgValue::Type::String && !it->second.String.empty())
{
try
{
return std::stoi(it->second.String);
}
catch (...)
{
}
}
return defaultValue;
}
// Headless execution mode - no GUI, just load and run the graph
int RunHeadless(const ArgsMap &args)
{
std::string logLevelOption = GetStringArg(args, "log-level", "debug");
bool logLevelValid = true;
auto parsedLogLevel = ParseLogLevel(logLevelOption, &logLevelValid);
InitLogger("blueprints-headless", true, true, "blueprints.log");
SetLoggerLevel(parsedLogLevel);
if (!logLevelValid)
{
LOG_WARN("Unknown log level '{}', defaulting to 'debug'", logLevelOption);
}
struct LoggerScope
{
~LoggerScope() { ShutdownLogger(); }
} loggerScope;
#if defined(_WIN32)
const MemoryUsageWin32 startMemory = CaptureMemoryUsageWin32();
const CpuTimesWin32 startCpuTimes = CaptureCpuTimesWin32();
const auto wallClockStart = std::chrono::steady_clock::now();
LogMemoryUsageWin32("Before run", startMemory);
#endif
struct RunTimer
{
std::chrono::steady_clock::time_point start{std::chrono::steady_clock::now()};
#if defined(_WIN32)
MemoryUsageWin32 startMemory;
CpuTimesWin32 startCpu;
std::chrono::steady_clock::time_point wallStart;
RunTimer(const MemoryUsageWin32 &memoryBaseline,
const CpuTimesWin32 &cpuBaseline,
std::chrono::steady_clock::time_point wallBaseline)
: startMemory(memoryBaseline), startCpu(cpuBaseline), wallStart(wallBaseline)
{
}
#else
RunTimer() = default;
#endif
~RunTimer()
{
auto elapsed = std::chrono::steady_clock::now() - start;
double seconds = std::chrono::duration_cast<std::chrono::duration<double>>(elapsed).count();
double deltaWorkingSetMb = 0.0;
double deltaPrivateMb = 0.0;
double cpuPercent = 0.0;
#if defined(_WIN32)
#if PRINT_STATS
MemoryUsageWin32 endMemory = CaptureMemoryUsageWin32();
LogMemoryUsageWin32("After run", endMemory);
deltaWorkingSetMb = (static_cast<double>(endMemory.workingSet) - static_cast<double>(startMemory.workingSet)) / (1024.0 * 1024.0);
deltaPrivateMb = (static_cast<double>(endMemory.privateBytes) - static_cast<double>(startMemory.privateBytes)) / (1024.0 * 1024.0);
auto wallElapsed = std::chrono::steady_clock::now() - wallStart;
double wallSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(wallElapsed).count();
CpuTimesWin32 endCpu = CaptureCpuTimesWin32();
cpuPercent = ComputeCpuUsagePercentWin32(startCpu, endCpu, wallSeconds);
LOG_INFO("Total execution time: {:.3f} s (ΔWorking Set: {:+.2f} MB, ΔPrivate: {:+.2f} MB, CPU Avg: {:.1f}%%)",
seconds, deltaWorkingSetMb, deltaPrivateMb, cpuPercent);
#endif
#endif
}
};
#if defined(_WIN32)
RunTimer runTimer(startMemory, startCpuTimes, wallClockStart);
#else
RunTimer runTimer;
#endif
LOG_INFO("==============================================");
LOG_INFO("HEADLESS MODE: Graph execution without GUI");
LOG_INFO("==============================================");
// Get graph file path (required)
std::string graphFile = GetStringArg(args, "graph", "");
if (graphFile.empty())
{
graphFile = GetStringArg(args, "file", "BlueprintsGraph.json");
}
if (graphFile.empty())
{
LOG_ERROR("ERROR: --graph or --file required in headless mode");
LOG_ERROR("Usage: --headless --graph=<path> --run [--log=all|blocks|links|none]");
return 1;
}
LOG_INFO("Graph file: {}", graphFile);
std::string logOption = GetStringArg(args, "log", "all");
// Check if we should execute (--run flag)
bool shouldRun = GetBoolArg(args, "run", false);
if (!shouldRun)
{
LOG_INFO("INFO: --run flag not specified, loading graph only (no execution)");
}
// Load JSON data
std::ifstream file(graphFile);
if (!file)
{
LOG_ERROR("ERROR: Failed to open graph file: {}", graphFile);
return 1;
}
std::string jsonData((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
auto root = crude_json::value::parse(jsonData);
if (root.is_discarded() || !root.is_object())
{
LOG_ERROR("ERROR: Failed to parse JSON or not an object");
return 1;
}
// Count nodes and links for info
int nodeCount = 0;
int linkCount = 0;
if (root.contains("app_nodes") && root["app_nodes"].is_array())
{
const auto &nodesArray = root["app_nodes"].get<crude_json::array>();
nodeCount = static_cast<int>(nodesArray.size());
}
if (root.contains("app_links") && root["app_links"].is_array())
{
const auto &linksArray = root["app_links"].get<crude_json::array>();
linkCount = static_cast<int>(linksArray.size());
}
LOG_INFO("Graph contains: {} nodes, {} links", nodeCount, linkCount);
// If --run not specified, just show info and exit
if (!shouldRun)
{
LOG_INFO("\nGraph info:\n Nodes: {}\n Links: {}\n\nUse --run flag to execute the graph", nodeCount, linkCount);
return 0;
}
LOG_INFO("==============================================");
LOG_INFO("EXECUTING GRAPH IN HEADLESS MODE");
LOG_INFO("==============================================");
// Create minimal ImGui context for state management (no window/rendering)
ImGuiContext *context = ImGui::CreateContext();
ImGui::SetCurrentContext(context);
App *app = new App("Blueprints_Headless", args);
// Initialize the app's graph state without creating window
// OnStart() will load the graph from the file specified in args
app->OnStart();
RootContainer *rootContainer = app->GetActiveRootContainer();
if (!rootContainer)
{
LOG_ERROR("ERROR: Failed to get root container");
delete app;
ImGui::DestroyContext(context);
return 1;
}
std::vector<Node *> startBlocks;
auto nodes = rootContainer->GetAllNodes();
for (auto *node : nodes)
{
if (node && node->IsBlockBased() && node->BlockInstance)
{
// Check if this is a Start block (block type contains "Start")
const char *typeName = node->BlockInstance->GetTypeName();
if (typeName && strstr(typeName, "Start") != nullptr)
{
startBlocks.push_back(node);
}
}
}
if (startBlocks.empty())
{
LOG_WARN("WARNING: No Start blocks found in graph");
delete app;
ImGui::DestroyContext(context);
return 0;
}
// Activate all Start blocks by setting their first output (flow) as active
for (auto *startNode : startBlocks)
{
if (startNode->BlockInstance)
{
// Run the Start block to print its message
startNode->BlockInstance->Run(*startNode, app);
// Activate the first flow output (index 0)
startNode->BlockInstance->ActivateOutput(0, true);
}
}
LOG_INFO("\n==============================================");
LOG_INFO("EXECUTING RUNTIME");
LOG_INFO("==============================================");
#if PRINT_STATS
const int maxRuntimeSteps = GetIntArg(args, "max-steps", 100);
if (maxRuntimeSteps > 0)
{
LOG_INFO("Max runtime steps: {} (override with --max-steps)", maxRuntimeSteps);
}
else
{
LOG_INFO("Max runtime steps: unlimited (use --max-steps to cap)");
}
#endif
#if PRINT_STATS
// Execute the runtime to propagate execution through the graph
// This will process all activated blocks and propagate through connections
int stepsExecuted = 0;
while (app->ExecuteRuntimeStep())
{
++stepsExecuted;
if (maxRuntimeSteps > 0 && stepsExecuted >= maxRuntimeSteps)
{
LOG_WARN("Reached max runtime steps ({}); stopping to prevent infinite loop", maxRuntimeSteps);
break;
}
}
double stepsPerSecond = stepsExecuted > 0 ? stepsExecuted / std::max(std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - wallClockStart).count(), 1e-6) : 0.0;
LOG_INFO("\n==============================================");
LOG_INFO("EXECUTION COMPLETE");
LOG_INFO("==============================================");
LOG_INFO("Runtime iterations executed: {} (Steps/sec: {:.2f})", stepsExecuted, stepsPerSecond);
#endif
delete app;
ImGui::DestroyContext(context);
return 0;
}
int Main(const ArgsMap &args)
{
if (!g_logger)
{
InitLogger("blueprints", true, true, "blueprints.log");
}
LOG_TRACE("[CHECKPOINT] Main: Beginning");
// Check for headless mode
bool headless = GetBoolArg(args, "headless", false);
if (headless)
{
LOG_TRACE("[CHECKPOINT] Main: Headless mode detected");
return RunHeadless(args);
}
// GUI mode (existing code)
App example("Blueprints", args);
LOG_TRACE("[CHECKPOINT] Main: App created, about to call Create()");
if (example.Create())
{
LOG_TRACE("[CHECKPOINT] Main: App created successfully, about to Run()");
return example.Run();
}
LOG_TRACE("[CHECKPOINT] Main: App.Create() returned false");
return 0;
}