#ifndef UNICODE #define UNICODE #endif #ifndef _UNICODE #define _UNICODE #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #include #include #include #include #include #include #include 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 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& 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 buffer(static_cast(length) + 1, L'\0'); GetWindowTextW(hwnd, buffer.data(), static_cast(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(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(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(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(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::max(); int best_intersections = std::numeric_limits::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::max() : best_crossings + best_intersections; const int peak = std::max(crossings, intersections); const int best_peak = best.empty() ? std::numeric_limits::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::max() / 4; int best_intersections = std::numeric_limits::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& 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 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(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(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(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(static_cast(id)), nullptr, nullptr); SendMessageW(hwnd, WM_SETFONT, reinterpret_cast(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(static_cast(id)), nullptr, nullptr); SendMessageW(hwnd, WM_SETFONT, reinterpret_cast(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(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(LOG_TEXT_LIMIT), 0); SendMessageW(g_log, WM_SETFONT, reinterpret_cast(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(lparam); append_log_direct(*text); delete text; return 0; } case WM_PROCESS_DONE: { append_log_direct(L"Завершено: exit code " + std::to_wstring(static_cast(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(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(msg.wParam); }