Files
Polyhedron/projects/PolyhedronGui/launcher_gui.cpp
T
Efim Beshmenev 7f9dc067a1 Initial commit
2026-07-10 19:16:42 +03:00

862 lines
31 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <shellapi.h>
#include <algorithm>
#include <atomic>
#include <filesystem>
#include <limits>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
namespace fs = std::filesystem;
namespace {
constexpr UINT WM_APPEND_LOG = WM_APP + 1;
constexpr UINT WM_PROCESS_DONE = WM_APP + 2;
constexpr int LOG_TEXT_LIMIT = 8 * 1024 * 1024;
constexpr int LOG_TRIM_KEEP = 6 * 1024 * 1024;
constexpr int LOG_APPEND_MARGIN = 32768;
constexpr int ID_SEED = 1001;
constexpr int ID_ITERS = 1002;
constexpr int ID_SIGMA = 1003;
constexpr int ID_BETA = 1004;
constexpr int ID_TEMP = 1005;
constexpr int ID_CLUSTERS = 1006;
constexpr int ID_MINUTES = 1007;
constexpr int ID_THREADS = 1008;
constexpr int ID_VERIFY_BASELINE = 2001;
constexpr int ID_REPAIR_LOCAL = 2002;
constexpr int ID_STUDY = 2003;
constexpr int ID_VERIFY_LAST = 2004;
constexpr int ID_STOP = 2005;
constexpr int ID_OPEN_CANDIDATES = 2006;
constexpr int ID_OPEN_REPORTS = 2007;
constexpr int ID_OPEN_BASELINE = 2008;
constexpr int ID_CLEAR_LOG = 2009;
constexpr int ID_CONTINUE_BEST = 2010;
constexpr int ID_GLOBAL_SEARCH = 2011;
constexpr int ID_OPEN_GLOBAL = 2012;
HWND g_main_window = nullptr;
HWND g_log = nullptr;
HWND g_status = nullptr;
HWND g_seed = nullptr;
HWND g_iters = nullptr;
HWND g_sigma = nullptr;
HWND g_beta = nullptr;
HWND g_temp = nullptr;
HWND g_clusters = nullptr;
HWND g_minutes = nullptr;
HWND g_threads = nullptr;
HWND g_stop = nullptr;
HFONT g_ui_font = nullptr;
HFONT g_title_font = nullptr;
HFONT g_mono_font = nullptr;
fs::path g_root;
fs::path g_stop_file;
PROCESS_INFORMATION g_process{};
std::atomic<bool> g_running{false};
std::wstring quote_arg(const std::wstring& value) {
if (value.find_first_of(L" \t\"") == std::wstring::npos) {
return value;
}
std::wstring quoted = L"\"";
for (wchar_t ch : value) {
if (ch == L'"') {
quoted += L"\\\"";
} else {
quoted += ch;
}
}
quoted += L"\"";
return quoted;
}
std::wstring join_args(const std::vector<std::wstring>& args) {
std::wstring result;
for (const std::wstring& arg : args) {
if (!result.empty()) {
result += L" ";
}
result += quote_arg(arg);
}
return result;
}
std::wstring get_text(HWND hwnd) {
const int length = GetWindowTextLengthW(hwnd);
std::vector<wchar_t> buffer(static_cast<size_t>(length) + 1, L'\0');
GetWindowTextW(hwnd, buffer.data(), static_cast<int>(buffer.size()));
return std::wstring(buffer.data());
}
std::wstring now_time() {
SYSTEMTIME st{};
GetLocalTime(&st);
wchar_t buffer[32]{};
swprintf_s(buffer, L"%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
return buffer;
}
void post_log(const std::wstring& text) {
PostMessageW(g_main_window, WM_APPEND_LOG, 0, reinterpret_cast<LPARAM>(new std::wstring(text)));
}
std::wstring ansi_to_wide(const char* data, int length) {
if (length <= 0) {
return L"";
}
int needed = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, data, length, nullptr, 0);
UINT codepage = CP_UTF8;
DWORD flags = MB_ERR_INVALID_CHARS;
if (needed <= 0) {
codepage = CP_ACP;
flags = 0;
needed = MultiByteToWideChar(codepage, flags, data, length, nullptr, 0);
}
if (needed <= 0) {
return L"";
}
std::wstring result(needed, L'\0');
MultiByteToWideChar(codepage, flags, data, length, result.data(), needed);
return result;
}
void append_log_direct(const std::wstring& text) {
const std::wstring line = L"[" + now_time() + L"] " + text + L"\r\n";
const int length = GetWindowTextLengthW(g_log);
if (length + static_cast<int>(line.size()) > LOG_TEXT_LIMIT - LOG_APPEND_MARGIN) {
const int remove_until = std::max(0, length - LOG_TRIM_KEEP);
if (remove_until > 0) {
SendMessageW(g_log, WM_SETREDRAW, FALSE, 0);
SendMessageW(g_log, EM_SETSEL, 0, remove_until);
SendMessageW(g_log, EM_REPLACESEL, FALSE, reinterpret_cast<LPARAM>(L"[... old log trimmed ...]\r\n"));
SendMessageW(g_log, WM_SETREDRAW, TRUE, 0);
InvalidateRect(g_log, nullptr, TRUE);
}
}
const int end = GetWindowTextLengthW(g_log);
SendMessageW(g_log, EM_SETSEL, end, end);
SendMessageW(g_log, EM_REPLACESEL, FALSE, reinterpret_cast<LPARAM>(line.c_str()));
SendMessageW(g_log, EM_SCROLLCARET, 0, 0);
}
void set_running_state(bool running, const std::wstring& status) {
g_running = running;
EnableWindow(g_stop, running ? TRUE : FALSE);
SetWindowTextW(g_status, status.c_str());
}
fs::path find_root() {
wchar_t buffer[MAX_PATH]{};
GetModuleFileNameW(nullptr, buffer, MAX_PATH);
fs::path candidate = fs::path(buffer).parent_path();
while (!candidate.empty()) {
if (fs::exists(candidate / L"Szilassi.slnx") &&
fs::exists(candidate / L"data" / L"shape_c2_i0_0.obj")) {
return candidate;
}
const fs::path parent = candidate.parent_path();
if (parent == candidate) {
break;
}
candidate = parent;
}
return fs::path(buffer).parent_path();
}
fs::path main_exe() {
const fs::path local = g_root / L"build" / L"msbuild" / L"bin" / L"x64" / L"Release" / L"Szilassi.exe";
if (fs::exists(local)) {
return local;
}
return g_root / L"build" / L"vs2026" / L"Release" / L"neighborly_main.exe";
}
fs::path verify_exe() {
const fs::path local = g_root / L"build" / L"msbuild" / L"bin" / L"x64" / L"Release" / L"VerifyCpp.exe";
if (fs::exists(local)) {
return local;
}
return g_root / L"build" / L"vs2026" / L"Release" / L"verify_cpp.exe";
}
void open_path(const fs::path& path) {
if (!fs::exists(path)) {
fs::create_directories(path);
}
ShellExecuteW(nullptr, L"open", path.c_str(), nullptr, g_root.c_str(), SW_SHOWNORMAL);
}
void open_file(const fs::path& path) {
if (!fs::exists(path)) {
MessageBoxW(g_main_window, (L"Файл пока не создан:\n" + path.wstring()).c_str(), L"Нет файла", MB_OK | MB_ICONINFORMATION);
return;
}
ShellExecuteW(nullptr, L"open", path.c_str(), nullptr, g_root.c_str(), SW_SHOWNORMAL);
}
fs::path latest_candidate() {
const fs::path dir = g_root / L"runtime" / L"candidates";
fs::path best;
fs::file_time_type best_time{};
if (!fs::exists(dir)) {
return best;
}
for (const fs::directory_entry& entry : fs::directory_iterator(dir)) {
if (!entry.is_regular_file() || entry.path().extension() != L".obj") {
continue;
}
const auto time = entry.last_write_time();
if (best.empty() || time > best_time) {
best = entry.path();
best_time = time;
}
}
return best;
}
bool parse_candidate_score(const fs::path& path, int& crossings, int& intersections, bool& strict) {
const std::wstring name = path.stem().wstring();
strict = name.find(L"_strict_c") != std::wstring::npos;
const size_t c_pos = name.rfind(L"_c");
if (c_pos == std::wstring::npos) {
return false;
}
const size_t i_pos = name.find(L"_i", c_pos + 2);
if (i_pos == std::wstring::npos) {
return false;
}
const size_t end_pos = name.find(L"_", i_pos + 2);
try {
crossings = std::stoi(name.substr(c_pos + 2, i_pos - (c_pos + 2)));
intersections = std::stoi(name.substr(i_pos + 2, end_pos == std::wstring::npos ? end_pos : end_pos - (i_pos + 2)));
} catch (...) {
return false;
}
return crossings >= 0 && intersections >= 0;
}
fs::path best_scored_candidate() {
const fs::path dir = g_root / L"runtime" / L"candidates";
fs::path best;
fs::file_time_type best_time{};
int best_crossings = std::numeric_limits<int>::max();
int best_intersections = std::numeric_limits<int>::max();
if (!fs::exists(dir)) {
return best;
}
for (const fs::directory_entry& entry : fs::directory_iterator(dir)) {
if (!entry.is_regular_file() || entry.path().extension() != L".obj") {
continue;
}
const std::wstring stem = entry.path().stem().wstring();
if (stem.rfind(L"smoke_", 0) == 0 || stem.rfind(L"audit_", 0) == 0) {
continue;
}
int crossings = 0;
int intersections = 0;
bool strict = false;
if (!parse_candidate_score(entry.path(), crossings, intersections, strict)) {
continue;
}
if (!strict) {
continue;
}
fs::path state_path = entry.path();
state_path.replace_extension(L".planes");
if (!fs::exists(state_path)) {
continue;
}
const auto time = entry.last_write_time();
const int defects = crossings + intersections;
const int best_defects = best.empty()
? std::numeric_limits<int>::max()
: best_crossings + best_intersections;
const int peak = std::max(crossings, intersections);
const int best_peak = best.empty()
? std::numeric_limits<int>::max()
: std::max(best_crossings, best_intersections);
const bool better =
best.empty() ||
defects < best_defects ||
(defects == best_defects && peak < best_peak) ||
(defects == best_defects && peak == best_peak && crossings < best_crossings) ||
(defects == best_defects && peak == best_peak && crossings == best_crossings && time > best_time);
if (better) {
best = entry.path();
best_time = time;
best_crossings = crossings;
best_intersections = intersections;
}
}
return best;
}
bool better_candidate_score(
int crossings,
int intersections,
const fs::file_time_type& time,
int best_crossings,
int best_intersections,
const fs::file_time_type& best_time
) {
const int defects = crossings + intersections;
const int best_defects = best_crossings + best_intersections;
const int peak = std::max(crossings, intersections);
const int best_peak = std::max(best_crossings, best_intersections);
return defects < best_defects ||
(defects == best_defects && peak < best_peak) ||
(defects == best_defects && peak == best_peak && crossings < best_crossings) ||
(defects == best_defects && peak == best_peak &&
crossings == best_crossings && time > best_time);
}
fs::path best_global_candidate() {
const fs::path root = g_root / L"runtime" / L"global_search";
if (!fs::exists(root)) {
return {};
}
fs::path best;
fs::file_time_type best_time{};
int best_crossings = std::numeric_limits<int>::max() / 4;
int best_intersections = std::numeric_limits<int>::max() / 4;
for (const fs::directory_entry& entry : fs::recursive_directory_iterator(root)) {
if (!entry.is_regular_file() || entry.path().extension() != L".obj") {
continue;
}
int crossings = 0;
int intersections = 0;
bool strict = false;
if (!parse_candidate_score(entry.path(), crossings, intersections, strict) || !strict) {
continue;
}
fs::path state_path = entry.path();
state_path.replace_extension(L".planes");
if (!fs::exists(state_path)) {
continue;
}
const auto time = entry.last_write_time();
if (best.empty() || better_candidate_score(
crossings,
intersections,
time,
best_crossings,
best_intersections,
best_time)) {
best = entry.path();
best_time = time;
best_crossings = crossings;
best_intersections = intersections;
}
}
return best;
}
fs::path best_any_candidate() {
const fs::path local = best_scored_candidate();
const fs::path global = best_global_candidate();
if (local.empty()) {
return global;
}
if (global.empty()) {
return local;
}
int local_c = 0;
int local_i = 0;
int global_c = 0;
int global_i = 0;
bool local_strict = false;
bool global_strict = false;
parse_candidate_score(local, local_c, local_i, local_strict);
parse_candidate_score(global, global_c, global_i, global_strict);
return better_candidate_score(
global_c,
global_i,
fs::last_write_time(global),
local_c,
local_i,
fs::last_write_time(local)) ? global : local;
}
int candidate_topology(const fs::path& candidate) {
const std::wstring parent = candidate.parent_path().filename().wstring();
if (parent.rfind(L"topology_", 0) == 0) {
try {
return std::stoi(parent.substr(9));
} catch (...) {
}
}
return 4;
}
void close_process_handles() {
if (g_process.hProcess) {
CloseHandle(g_process.hProcess);
g_process.hProcess = nullptr;
}
if (g_process.hThread) {
CloseHandle(g_process.hThread);
g_process.hThread = nullptr;
}
}
void start_process(
const std::wstring& title,
const fs::path& exe,
const std::vector<std::wstring>& args,
const fs::path& stop_file = {}
) {
if (g_running) {
MessageBoxW(g_main_window, L"Сначала останови текущий процесс.", L"Уже запущено", MB_OK | MB_ICONINFORMATION);
return;
}
if (!fs::exists(exe)) {
MessageBoxW(g_main_window, (L"Не найден exe:\n" + exe.wstring()).c_str(), L"Файл не найден", MB_OK | MB_ICONWARNING);
return;
}
fs::create_directories(g_root / L"runtime" / L"candidates");
fs::create_directories(g_root / L"runtime" / L"reports");
SECURITY_ATTRIBUTES sa{};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
HANDLE read_pipe = nullptr;
HANDLE write_pipe = nullptr;
if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) {
MessageBoxW(g_main_window, L"Не удалось создать pipe для вывода.", L"Ошибка", MB_OK | MB_ICONERROR);
return;
}
SetHandleInformation(read_pipe, HANDLE_FLAG_INHERIT, 0);
std::wstring command = quote_arg(exe.wstring());
const std::wstring argument_string = join_args(args);
if (!argument_string.empty()) {
command += L" " + argument_string;
}
STARTUPINFOW si{};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = write_pipe;
si.hStdError = write_pipe;
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
close_process_handles();
PROCESS_INFORMATION pi{};
std::vector<wchar_t> mutable_command(command.begin(), command.end());
mutable_command.push_back(L'\0');
post_log(L"=== " + title + L" ===");
post_log(command);
const BOOL ok = CreateProcessW(
nullptr,
mutable_command.data(),
nullptr,
nullptr,
TRUE,
CREATE_NO_WINDOW,
nullptr,
g_root.c_str(),
&si,
&pi
);
CloseHandle(write_pipe);
if (!ok) {
CloseHandle(read_pipe);
g_stop_file.clear();
MessageBoxW(g_main_window, L"Не удалось запустить процесс.", L"Ошибка", MB_OK | MB_ICONERROR);
return;
}
g_process = pi;
g_stop_file = stop_file;
set_running_state(true, L"Выполняется: " + title);
std::thread([read_pipe, pi]() {
char buffer[4096];
DWORD read = 0;
while (ReadFile(read_pipe, buffer, sizeof(buffer), &read, nullptr) && read > 0) {
post_log(ansi_to_wide(buffer, static_cast<int>(read)));
}
CloseHandle(read_pipe);
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
PostMessageW(g_main_window, WM_PROCESS_DONE, static_cast<WPARAM>(exit_code), 0);
}).detach();
}
HWND make_label(HWND parent, const std::wstring& text, int x, int y, int w, int h) {
HWND hwnd = CreateWindowExW(0, L"STATIC", text.c_str(), WS_CHILD | WS_VISIBLE, x, y, w, h, parent, nullptr, nullptr, nullptr);
SendMessageW(hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
return hwnd;
}
HWND make_edit(HWND parent, int id, const std::wstring& text, int x, int y, int w, int h) {
HWND hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", text.c_str(), WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, x, y, w, h, parent, reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)), nullptr, nullptr);
SendMessageW(hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
return hwnd;
}
HWND make_button(HWND parent, int id, const std::wstring& text, int x, int y, int w, int h) {
HWND hwnd = CreateWindowExW(0, L"BUTTON", text.c_str(), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, x, y, w, h, parent, reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)), nullptr, nullptr);
SendMessageW(hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
return hwnd;
}
void create_controls(HWND hwnd) {
g_title_font = CreateFontW(24, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
g_ui_font = CreateFontW(17, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
g_mono_font = CreateFontW(15, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas");
HWND title = make_label(hwnd, L"K12 Neighborly Polyhedron", 18, 14, 430, 32);
SendMessageW(title, WM_SETFONT, reinterpret_cast<WPARAM>(g_title_font), TRUE);
make_label(hwnd, L"Папка проекта: " + g_root.wstring(), 20, 50, 900, 24);
make_label(hwnd, L"Seed", 22, 90, 95, 22);
g_seed = make_edit(hwnd, ID_SEED, L"30000157", 22, 114, 95, 26);
make_label(hwnd, L"Итераций", 139, 90, 95, 22);
g_iters = make_edit(hwnd, ID_ITERS, L"30000", 139, 114, 95, 26);
make_label(hwnd, L"Попыток t4", 256, 90, 95, 22);
g_clusters = make_edit(hwnd, ID_CLUSTERS, L"100000", 256, 114, 95, 26);
make_label(hwnd, L"Минут", 373, 90, 95, 22);
g_minutes = make_edit(hwnd, ID_MINUTES, L"480", 373, 114, 95, 26);
make_label(hwnd, L"Потоков", 490, 90, 95, 22);
g_threads = make_edit(hwnd, ID_THREADS, L"0", 490, 114, 95, 26);
make_label(hwnd, L"Шаг", 607, 90, 95, 22);
g_sigma = make_edit(hwnd, ID_SIGMA, L"0.25", 607, 114, 95, 26);
make_label(hwnd, L"Temp", 724, 90, 95, 22);
g_temp = make_edit(hwnd, ID_TEMP, L"0.02", 724, 114, 95, 26);
make_label(hwnd, L"Beta", 841, 90, 95, 22);
g_beta = make_edit(hwnd, ID_BETA, L"0.9995", 841, 114, 95, 26);
make_button(hwnd, ID_GLOBAL_SEARCH, L"Глобальный поиск 59", 22, 164, 220, 36);
make_button(hwnd, ID_CONTINUE_BEST, L"Продолжить topology 4", 254, 164, 210, 36);
make_button(hwnd, ID_REPAIR_LOCAL, L"Начать t4 заново", 476, 164, 180, 36);
make_button(hwnd, ID_VERIFY_LAST, L"Проверить лучший", 668, 164, 170, 36);
g_stop = make_button(hwnd, ID_STOP, L"Остановить", 850, 164, 110, 36);
EnableWindow(g_stop, FALSE);
make_button(hwnd, ID_VERIFY_BASELINE, L"Проверить baseline", 22, 212, 180, 34);
make_button(hwnd, ID_OPEN_GLOBAL, L"Открыть global", 214, 212, 160, 34);
make_button(hwnd, ID_OPEN_CANDIDATES, L"Открыть candidates", 386, 212, 180, 34);
make_button(hwnd, ID_OPEN_REPORTS, L"Открыть reports", 578, 212, 160, 34);
make_button(hwnd, ID_CLEAR_LOG, L"Очистить лог", 750, 212, 140, 34);
g_status = make_label(hwnd, L"Готово", 22, 260, 900, 24);
g_log = CreateWindowExW(
WS_EX_CLIENTEDGE,
L"EDIT",
L"",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
18,
290,
942,
360,
hwnd,
nullptr,
nullptr,
nullptr
);
SendMessageW(g_log, EM_SETLIMITTEXT, static_cast<WPARAM>(LOG_TEXT_LIMIT), 0);
SendMessageW(g_log, WM_SETFONT, reinterpret_cast<WPARAM>(g_mono_font), TRUE);
append_log_direct(L"GUI готов. Двойной клик: build\\msbuild\\bin\\x64\\Release\\PolyhedronGui.exe");
append_log_direct(L"Основной exe: " + main_exe().wstring());
append_log_direct(L"Verifier exe: " + verify_exe().wstring());
}
void resize_controls(HWND hwnd) {
RECT rc{};
GetClientRect(hwnd, &rc);
const int width = rc.right - rc.left;
const int height = rc.bottom - rc.top;
MoveWindow(g_log, 18, 290, std::max(300, width - 36), std::max(120, height - 310), TRUE);
}
void handle_command(int id) {
switch (id) {
case ID_GLOBAL_SEARCH: {
SYSTEMTIME st{};
GetLocalTime(&st);
wchar_t stamp[64]{};
swprintf_s(stamp, L"%04d%02d%02d_%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
const fs::path stop_path = g_root / L"runtime" / L"reports" / (std::wstring(L"global_stop_") + stamp + L".flag");
start_process(L"Глобальный поиск по 59 топологиям", main_exe(), {
L"--global-search",
L"--seed", get_text(g_seed),
L"--iters", get_text(g_iters),
L"--threads", get_text(g_threads),
L"--minutes", get_text(g_minutes),
L"--stop-file", stop_path.wstring(),
L"--sigma", get_text(g_sigma),
L"--beta", get_text(g_beta),
L"--temperature", get_text(g_temp),
L"--restarts", L"512",
L"--stagnation", L"1500",
L"--jump-chance", L"0.12",
L"--global-dir", L"runtime\\global_search"
}, stop_path);
break;
}
case ID_VERIFY_BASELINE:
start_process(L"Проверка near-miss", verify_exe(), {
L"--obj", L"data\\shape_c2_i0_0.obj",
L"--topology", L"4",
L"--report", L"runtime\\reports\\00_baseline.md",
L"--json", L"runtime\\reports\\baseline.json"
});
break;
case ID_REPAIR_LOCAL: {
SYSTEMTIME st{};
GetLocalTime(&st);
wchar_t stamp[64]{};
swprintf_s(stamp, L"%04d%02d%02d_%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
const fs::path stop_path = g_root / L"runtime" / L"reports" / (std::wstring(L"gui_stop_") + stamp + L".flag");
start_process(L"Поиск с baseline", main_exe(), {
L"--batch-hunt", L"data\\shape_c2_i0_0.obj",
L"--topology", L"4",
L"--seed", get_text(g_seed),
L"--iters", get_text(g_iters),
L"--trials", get_text(g_clusters),
L"--threads", get_text(g_threads),
L"--minutes", get_text(g_minutes),
L"--stop-file", stop_path.wstring(),
L"--sigma", get_text(g_sigma),
L"--beta", get_text(g_beta),
L"--temperature", get_text(g_temp),
L"--report-every", L"100",
L"--restarts", L"512",
L"--stagnation", L"1500",
L"--jump-chance", L"0.12",
L"--out", std::wstring(L"runtime\\candidates\\gui_batch_") + stamp,
L"--report", L"runtime\\reports\\gui_batch_hunt.md"
}, stop_path);
break;
}
case ID_CONTINUE_BEST: {
const fs::path candidate = best_scored_candidate();
if (candidate.empty()) {
append_log_direct(L"Сохранённого состояния пока нет; начинаю с baseline.");
handle_command(ID_REPAIR_LOCAL);
return;
}
fs::path state_path = candidate;
state_path.replace_extension(L".planes");
SYSTEMTIME st{};
GetLocalTime(&st);
wchar_t stamp[64]{};
swprintf_s(stamp, L"%04d%02d%02d_%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
const fs::path stop_path = g_root / L"runtime" / L"reports" / (std::wstring(L"gui_stop_") + stamp + L".flag");
append_log_direct(L"Продолжаю от лучшего OBJ: " + candidate.wstring());
start_process(L"Поиск от лучшего", main_exe(), {
L"--batch-hunt", candidate.wstring(),
L"--topology", L"4",
L"--seed", get_text(g_seed),
L"--iters", get_text(g_iters),
L"--trials", get_text(g_clusters),
L"--threads", get_text(g_threads),
L"--minutes", get_text(g_minutes),
L"--stop-file", stop_path.wstring(),
L"--sigma", get_text(g_sigma),
L"--beta", get_text(g_beta),
L"--temperature", get_text(g_temp),
L"--report-every", L"100",
L"--restarts", L"512",
L"--stagnation", L"1500",
L"--jump-chance", L"0.12",
L"--out", std::wstring(L"runtime\\candidates\\gui_continue_") + stamp,
L"--report", L"runtime\\reports\\gui_continue_hunt.md",
L"--start-planes", state_path.wstring()
}, stop_path);
break;
}
case ID_STUDY: {
SYSTEMTIME st{};
GetLocalTime(&st);
wchar_t stamp[64]{};
swprintf_s(stamp, L"%04d%02d%02d_%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
start_process(L"study", main_exe(), {
L"--study", L"data\\shape_c2_i0_0.obj",
L"--topology", L"4",
L"--seed", get_text(g_seed),
L"--iters", get_text(g_iters),
L"--clusters", get_text(g_clusters),
L"--sigma", get_text(g_sigma),
L"--objective", L"cross-zint",
L"--out", std::wstring(L"runtime\\candidates\\gui_study_") + stamp,
L"--report", L"runtime\\reports\\gui_study.md"
});
break;
}
case ID_VERIFY_LAST: {
const fs::path candidate = best_any_candidate();
if (candidate.empty()) {
MessageBoxW(g_main_window, L"Сохранённого strict-кандидата пока нет.", L"Нет кандидатов", MB_OK | MB_ICONINFORMATION);
return;
}
const std::wstring topology = std::to_wstring(candidate_topology(candidate));
start_process(L"Проверка лучшего OBJ", verify_exe(), {
L"--obj", candidate.wstring(),
L"--topology", topology,
L"--report", L"runtime\\reports\\last_candidate_verify.md",
L"--json", L"runtime\\reports\\last_candidate_verify.json"
});
break;
}
case ID_STOP:
if (g_running && g_process.hProcess) {
if (!g_stop_file.empty()) {
append_log_direct(L"Запрошена штатная остановка; сохраняю результат...");
HANDLE stop_file = CreateFileW(
g_stop_file.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (stop_file != INVALID_HANDLE_VALUE) {
CloseHandle(stop_file);
EnableWindow(g_stop, FALSE);
SetWindowTextW(g_status, L"Останавливается и сохраняет...");
} else {
TerminateProcess(g_process.hProcess, 1);
}
} else {
append_log_direct(L"Остановка процесса...");
TerminateProcess(g_process.hProcess, 1);
}
}
break;
case ID_OPEN_CANDIDATES:
open_path(g_root / L"runtime" / L"candidates");
break;
case ID_OPEN_GLOBAL:
open_path(g_root / L"runtime" / L"global_search");
break;
case ID_OPEN_REPORTS:
open_path(g_root / L"runtime" / L"reports");
break;
case ID_OPEN_BASELINE:
open_file(g_root / L"runtime" / L"reports" / L"00_baseline.md");
break;
case ID_CLEAR_LOG:
SetWindowTextW(g_log, L"");
break;
}
}
LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_CREATE:
g_main_window = hwnd;
create_controls(hwnd);
return 0;
case WM_SIZE:
resize_controls(hwnd);
return 0;
case WM_COMMAND:
handle_command(LOWORD(wparam));
return 0;
case WM_APPEND_LOG: {
std::wstring* text = reinterpret_cast<std::wstring*>(lparam);
append_log_direct(*text);
delete text;
return 0;
}
case WM_PROCESS_DONE: {
append_log_direct(L"Завершено: exit code " + std::to_wstring(static_cast<DWORD>(wparam)));
set_running_state(false, L"Готово");
close_process_handles();
if (!g_stop_file.empty()) {
std::error_code remove_error;
fs::remove(g_stop_file, remove_error);
g_stop_file.clear();
}
return 0;
}
case WM_CLOSE:
if (g_running) {
const int answer = MessageBoxW(hwnd, L"Процесс еще работает. Остановить и закрыть окно?", L"Процесс работает", MB_YESNO | MB_ICONQUESTION);
if (answer != IDYES) {
return 0;
}
if (g_process.hProcess) {
TerminateProcess(g_process.hProcess, 1);
}
}
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
close_process_handles();
DeleteObject(g_ui_font);
DeleteObject(g_title_font);
DeleteObject(g_mono_font);
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
} // namespace
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show_command) {
g_root = find_root();
WNDCLASSW wc{};
wc.lpfnWndProc = window_proc;
wc.hInstance = instance;
wc.lpszClassName = L"PolyhedronGuiWindow";
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
RegisterClassW(&wc);
HWND hwnd = CreateWindowExW(
0,
wc.lpszClassName,
L"Polyhedron Control",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1000,
720,
nullptr,
nullptr,
instance,
nullptr
);
ShowWindow(hwnd, show_command);
UpdateWindow(hwnd);
MSG msg{};
while (GetMessageW(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return static_cast<int>(msg.wParam);
}