862 lines
31 KiB
C++
862 lines
31 KiB
C++
#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);
|
||
}
|