908 lines
29 KiB
C++
908 lines
29 KiB
C++
#ifndef UNICODE
|
||
#define UNICODE
|
||
#endif
|
||
#ifndef _UNICODE
|
||
#define _UNICODE
|
||
#endif
|
||
#ifndef NOMINMAX
|
||
#define NOMINMAX
|
||
#endif
|
||
|
||
#include <windows.h>
|
||
|
||
#include <algorithm>
|
||
#include <cerrno>
|
||
#include <cstdint>
|
||
#include <cwchar>
|
||
#include <cwctype>
|
||
#include <filesystem>
|
||
#include <limits>
|
||
#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 TOPOLOGY_COUNT = 59;
|
||
|
||
constexpr int ID_CUDA_CHAINS = 1001;
|
||
constexpr int ID_CUDA_ITERS = 1002;
|
||
constexpr int ID_MINUTES = 1003;
|
||
constexpr int ID_TOPOLOGY_FROM = 1004;
|
||
constexpr int ID_TOPOLOGY_TO = 1005;
|
||
constexpr int ID_CHECKPOINT_SECONDS = 1006;
|
||
constexpr int ID_SEARCH_MODE = 1007;
|
||
constexpr int ID_START = 2001;
|
||
constexpr int ID_STOP = 2002;
|
||
|
||
enum class SearchState {
|
||
idle,
|
||
running,
|
||
stopping
|
||
};
|
||
|
||
HWND g_main_window = nullptr;
|
||
HWND g_log = nullptr;
|
||
HWND g_status = nullptr;
|
||
HWND g_start = nullptr;
|
||
HWND g_stop = nullptr;
|
||
HWND g_cuda_chains = nullptr;
|
||
HWND g_cuda_iters = nullptr;
|
||
HWND g_minutes = nullptr;
|
||
HWND g_topology_from = nullptr;
|
||
HWND g_topology_to = nullptr;
|
||
HWND g_checkpoint_seconds = nullptr;
|
||
HWND g_search_mode = nullptr;
|
||
std::vector<HWND> g_setting_controls;
|
||
|
||
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{};
|
||
SearchState g_search_state = SearchState::idle;
|
||
bool g_close_after_stop = false;
|
||
|
||
std::wstring quote_arg(const std::wstring& value) {
|
||
if (value.empty()) {
|
||
return L"\"\"";
|
||
}
|
||
if (value.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
|
||
return value;
|
||
}
|
||
|
||
std::wstring result;
|
||
result.push_back(L'"');
|
||
size_t backslash_count = 0;
|
||
for (const wchar_t ch : value) {
|
||
if (ch == L'\\') {
|
||
++backslash_count;
|
||
continue;
|
||
}
|
||
if (ch == L'"') {
|
||
result.append(backslash_count * 2 + 1, L'\\');
|
||
result.push_back(L'"');
|
||
} else {
|
||
result.append(backslash_count, L'\\');
|
||
result.push_back(ch);
|
||
}
|
||
backslash_count = 0;
|
||
}
|
||
result.append(backslash_count * 2, L'\\');
|
||
result.push_back(L'"');
|
||
return result;
|
||
}
|
||
|
||
std::wstring join_args(const std::vector<std::wstring>& args) {
|
||
std::wstring result;
|
||
for (const std::wstring& arg : args) {
|
||
if (!result.empty()) {
|
||
result.push_back(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());
|
||
}
|
||
|
||
int make_automatic_seed() {
|
||
LARGE_INTEGER counter{};
|
||
QueryPerformanceCounter(&counter);
|
||
std::uint64_t value = static_cast<std::uint64_t>(counter.QuadPart);
|
||
value ^= static_cast<std::uint64_t>(GetTickCount64()) << 21;
|
||
value ^= static_cast<std::uint64_t>(GetCurrentProcessId()) << 37;
|
||
value ^= value >> 30;
|
||
value *= 0xbf58476d1ce4e5b9ULL;
|
||
value ^= value >> 27;
|
||
return 1 + static_cast<int>(value % 2147483646ULL);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
std::wstring win32_error_message(DWORD error) {
|
||
wchar_t* message = nullptr;
|
||
const DWORD length = FormatMessageW(
|
||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||
nullptr,
|
||
error,
|
||
0,
|
||
reinterpret_cast<wchar_t*>(&message),
|
||
0,
|
||
nullptr);
|
||
if (length == 0 || message == nullptr) {
|
||
return L"код " + std::to_wstring(error);
|
||
}
|
||
std::wstring result(message, length);
|
||
LocalFree(message);
|
||
while (!result.empty() && (result.back() == L'\r' || result.back() == L'\n' || result.back() == L' ')) {
|
||
result.pop_back();
|
||
}
|
||
return result + L" (код " + std::to_wstring(error) + L")";
|
||
}
|
||
|
||
void post_log(const std::wstring& text) {
|
||
auto* payload = new std::wstring(text);
|
||
const HWND window = g_main_window;
|
||
if (window == nullptr ||
|
||
!PostMessageW(window, WM_APPEND_LOG, 0, reinterpret_cast<LPARAM>(payload))) {
|
||
delete payload;
|
||
}
|
||
}
|
||
|
||
std::wstring bytes_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(static_cast<size_t>(needed), L'\0');
|
||
MultiByteToWideChar(codepage, flags, data, length, result.data(), needed);
|
||
return result;
|
||
}
|
||
|
||
void append_log_direct(const std::wstring& text) {
|
||
if (g_log == nullptr) {
|
||
return;
|
||
}
|
||
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"[... старые строки журнала удалены ...]\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_search_state(SearchState state, const std::wstring& status) {
|
||
g_search_state = state;
|
||
const BOOL settings_enabled = state == SearchState::idle ? TRUE : FALSE;
|
||
for (HWND control : g_setting_controls) {
|
||
EnableWindow(control, settings_enabled);
|
||
}
|
||
EnableWindow(g_start, state == SearchState::idle ? TRUE : FALSE);
|
||
EnableWindow(g_stop, state == SearchState::running ? TRUE : FALSE);
|
||
if (g_status != nullptr) {
|
||
SetWindowTextW(g_status, status.c_str());
|
||
}
|
||
}
|
||
|
||
fs::path find_root() {
|
||
std::vector<wchar_t> buffer(32768, L'\0');
|
||
const DWORD length = GetModuleFileNameW(
|
||
nullptr,
|
||
buffer.data(),
|
||
static_cast<DWORD>(buffer.size()));
|
||
fs::path candidate = fs::path(std::wstring(buffer.data(), length)).parent_path();
|
||
while (!candidate.empty()) {
|
||
if (fs::exists(candidate / L"CMakeLists.txt") &&
|
||
fs::exists(candidate / L"data" / L"topologies.txt")) {
|
||
return candidate;
|
||
}
|
||
const fs::path parent = candidate.parent_path();
|
||
if (parent == candidate) {
|
||
break;
|
||
}
|
||
candidate = parent;
|
||
}
|
||
return fs::path(std::wstring(buffer.data(), length)).parent_path();
|
||
}
|
||
|
||
fs::path main_exe() {
|
||
const fs::path msbuild =
|
||
g_root / L"build" / L"msbuild" / L"bin" / L"x64" / L"Release" / L"Szilassi.exe";
|
||
if (fs::exists(msbuild)) {
|
||
return msbuild;
|
||
}
|
||
return g_root / L"build" / L"vs2026" / L"Release" / L"neighborly_main.exe";
|
||
}
|
||
|
||
void close_process_handles() {
|
||
if (g_process.hProcess != nullptr) {
|
||
CloseHandle(g_process.hProcess);
|
||
g_process.hProcess = nullptr;
|
||
}
|
||
if (g_process.hThread != nullptr) {
|
||
CloseHandle(g_process.hThread);
|
||
g_process.hThread = nullptr;
|
||
}
|
||
g_process.dwProcessId = 0;
|
||
g_process.dwThreadId = 0;
|
||
}
|
||
|
||
bool parse_integer_setting(
|
||
HWND control,
|
||
const wchar_t* name,
|
||
int minimum,
|
||
int maximum,
|
||
int& result
|
||
) {
|
||
const std::wstring text = get_text(control);
|
||
const wchar_t* first = text.c_str();
|
||
while (*first != L'\0' && iswspace(*first)) {
|
||
++first;
|
||
}
|
||
|
||
errno = 0;
|
||
wchar_t* end = nullptr;
|
||
const long long value = wcstoll(first, &end, 10);
|
||
while (end != nullptr && *end != L'\0' && iswspace(*end)) {
|
||
++end;
|
||
}
|
||
|
||
if (first == end || end == nullptr || *end != L'\0' || errno == ERANGE ||
|
||
value < minimum || value > maximum) {
|
||
const std::wstring message =
|
||
std::wstring(L"Поле «") + name + L"» должно содержать целое число от " +
|
||
std::to_wstring(minimum) + L" до " + std::to_wstring(maximum) + L".";
|
||
MessageBoxW(g_main_window, message.c_str(), L"Некорректные настройки", MB_OK | MB_ICONWARNING);
|
||
SetFocus(control);
|
||
SendMessageW(control, EM_SETSEL, 0, -1);
|
||
return false;
|
||
}
|
||
result = static_cast<int>(value);
|
||
return true;
|
||
}
|
||
|
||
fs::path make_stop_path() {
|
||
SYSTEMTIME st{};
|
||
GetLocalTime(&st);
|
||
wchar_t name[128]{};
|
||
swprintf_s(
|
||
name,
|
||
L"cuda_stop_%04d%02d%02d_%02d%02d%02d_%03d_pid%lu.flag",
|
||
st.wYear,
|
||
st.wMonth,
|
||
st.wDay,
|
||
st.wHour,
|
||
st.wMinute,
|
||
st.wSecond,
|
||
st.wMilliseconds,
|
||
static_cast<unsigned long>(GetCurrentProcessId()));
|
||
return g_root / L"runtime" / L"reports" / name;
|
||
}
|
||
|
||
bool start_process(
|
||
const std::wstring& title,
|
||
const fs::path& exe,
|
||
const std::vector<std::wstring>& args,
|
||
const fs::path& stop_file
|
||
) {
|
||
if (g_search_state != SearchState::idle) {
|
||
return false;
|
||
}
|
||
if (!fs::exists(exe)) {
|
||
const std::wstring message =
|
||
L"Не найден исполняемый файл:\n" + exe.wstring() +
|
||
L"\n\nСоберите preset vs2026-release через CMake или отдельно "
|
||
L"projects\\Szilassi\\Szilassi.vcxproj в конфигурации Release|x64.";
|
||
MessageBoxW(g_main_window, message.c_str(), L"Файл не найден", MB_OK | MB_ICONWARNING);
|
||
return false;
|
||
}
|
||
|
||
std::error_code directory_error;
|
||
fs::create_directories(stop_file.parent_path(), directory_error);
|
||
fs::create_directories(g_root / L"results" / L"search", directory_error);
|
||
std::error_code remove_error;
|
||
fs::remove(stop_file, remove_error);
|
||
if (remove_error || fs::exists(stop_file)) {
|
||
const std::wstring message =
|
||
L"Не удалось подготовить stop-файл:\n" + stop_file.wstring();
|
||
MessageBoxW(g_main_window, message.c_str(), L"Ошибка запуска", MB_OK | MB_ICONERROR);
|
||
return false;
|
||
}
|
||
|
||
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)) {
|
||
const DWORD error = GetLastError();
|
||
const std::wstring message =
|
||
L"Не удалось создать канал вывода:\n" + win32_error_message(error);
|
||
MessageBoxW(g_main_window, message.c_str(), L"Ошибка запуска", MB_OK | MB_ICONERROR);
|
||
return false;
|
||
}
|
||
if (!SetHandleInformation(read_pipe, HANDLE_FLAG_INHERIT, 0)) {
|
||
const DWORD error = GetLastError();
|
||
CloseHandle(read_pipe);
|
||
CloseHandle(write_pipe);
|
||
const std::wstring message =
|
||
L"Не удалось настроить канал вывода:\n" + win32_error_message(error);
|
||
MessageBoxW(g_main_window, message.c_str(), L"Ошибка запуска", MB_OK | MB_ICONERROR);
|
||
return false;
|
||
}
|
||
|
||
std::wstring command = quote_arg(exe.wstring());
|
||
const std::wstring argument_string = join_args(args);
|
||
if (!argument_string.empty()) {
|
||
command += L" " + argument_string;
|
||
}
|
||
|
||
STARTUPINFOW startup{};
|
||
startup.cb = sizeof(startup);
|
||
startup.dwFlags = STARTF_USESTDHANDLES;
|
||
startup.hStdOutput = write_pipe;
|
||
startup.hStdError = write_pipe;
|
||
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||
|
||
close_process_handles();
|
||
PROCESS_INFORMATION process{};
|
||
std::vector<wchar_t> mutable_command(command.begin(), command.end());
|
||
mutable_command.push_back(L'\0');
|
||
|
||
append_log_direct(L"=== " + title + L" ===");
|
||
append_log_direct(command);
|
||
const BOOL created = CreateProcessW(
|
||
nullptr,
|
||
mutable_command.data(),
|
||
nullptr,
|
||
nullptr,
|
||
TRUE,
|
||
CREATE_NO_WINDOW | BELOW_NORMAL_PRIORITY_CLASS,
|
||
nullptr,
|
||
g_root.c_str(),
|
||
&startup,
|
||
&process);
|
||
const DWORD create_error = created ? ERROR_SUCCESS : GetLastError();
|
||
CloseHandle(write_pipe);
|
||
|
||
if (!created) {
|
||
CloseHandle(read_pipe);
|
||
const std::wstring message =
|
||
L"Не удалось запустить поиск:\n" + win32_error_message(create_error);
|
||
MessageBoxW(g_main_window, message.c_str(), L"Ошибка запуска", MB_OK | MB_ICONERROR);
|
||
return false;
|
||
}
|
||
|
||
g_process = process;
|
||
g_stop_file = stop_file;
|
||
g_close_after_stop = false;
|
||
set_search_state(SearchState::running, L"Поиск выполняется");
|
||
|
||
std::thread([read_pipe, process]() {
|
||
char buffer[4096];
|
||
DWORD bytes_read = 0;
|
||
while (ReadFile(read_pipe, buffer, sizeof(buffer), &bytes_read, nullptr) && bytes_read > 0) {
|
||
post_log(bytes_to_wide(buffer, static_cast<int>(bytes_read)));
|
||
}
|
||
CloseHandle(read_pipe);
|
||
WaitForSingleObject(process.hProcess, INFINITE);
|
||
DWORD exit_code = 0;
|
||
GetExitCodeProcess(process.hProcess, &exit_code);
|
||
const HWND window = g_main_window;
|
||
if (window != nullptr) {
|
||
PostMessageW(window, WM_PROCESS_DONE, static_cast<WPARAM>(exit_code), 0);
|
||
}
|
||
}).detach();
|
||
return true;
|
||
}
|
||
|
||
bool create_stop_file() {
|
||
if (g_stop_file.empty()) {
|
||
return false;
|
||
}
|
||
HANDLE file = CreateFileW(
|
||
g_stop_file.c_str(),
|
||
GENERIC_WRITE,
|
||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||
nullptr,
|
||
CREATE_ALWAYS,
|
||
FILE_ATTRIBUTE_NORMAL,
|
||
nullptr);
|
||
if (file == INVALID_HANDLE_VALUE) {
|
||
const DWORD error = GetLastError();
|
||
const std::wstring message =
|
||
L"Не удалось запросить штатную остановку:\n" + win32_error_message(error) +
|
||
L"\n\nПоиск продолжает работать; данные не были принудительно прерваны.";
|
||
MessageBoxW(g_main_window, message.c_str(), L"Ошибка остановки", MB_OK | MB_ICONERROR);
|
||
append_log_direct(message);
|
||
return false;
|
||
}
|
||
|
||
static constexpr char marker[] = "stop\n";
|
||
DWORD written = 0;
|
||
WriteFile(file, marker, static_cast<DWORD>(sizeof(marker) - 1), &written, nullptr);
|
||
FlushFileBuffers(file);
|
||
CloseHandle(file);
|
||
return true;
|
||
}
|
||
|
||
void request_stop(bool close_after_stop) {
|
||
if (g_search_state == SearchState::idle) {
|
||
if (close_after_stop) {
|
||
DestroyWindow(g_main_window);
|
||
}
|
||
return;
|
||
}
|
||
if (g_search_state == SearchState::stopping) {
|
||
g_close_after_stop = g_close_after_stop || close_after_stop;
|
||
if (g_close_after_stop) {
|
||
SetWindowTextW(g_status, L"Останавливается, сохраняет checkpoint и закроется...");
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!create_stop_file()) {
|
||
return;
|
||
}
|
||
g_close_after_stop = close_after_stop;
|
||
append_log_direct(L"Запрошена штатная остановка. Ожидаю завершения CUDA-пакета и сохранения checkpoint...");
|
||
set_search_state(
|
||
SearchState::stopping,
|
||
close_after_stop
|
||
? L"Останавливается, сохраняет checkpoint и закроется..."
|
||
: L"Останавливается и сохраняет checkpoint...");
|
||
}
|
||
|
||
HWND make_label(HWND parent, const std::wstring& text, int x, int y, int width, int height) {
|
||
HWND control = CreateWindowExW(
|
||
0,
|
||
L"STATIC",
|
||
text.c_str(),
|
||
WS_CHILD | WS_VISIBLE,
|
||
x,
|
||
y,
|
||
width,
|
||
height,
|
||
parent,
|
||
nullptr,
|
||
nullptr,
|
||
nullptr);
|
||
SendMessageW(control, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
|
||
return control;
|
||
}
|
||
|
||
HWND make_number_edit(
|
||
HWND parent,
|
||
int id,
|
||
const std::wstring& text,
|
||
int x,
|
||
int y,
|
||
int width,
|
||
int height
|
||
) {
|
||
HWND control = CreateWindowExW(
|
||
WS_EX_CLIENTEDGE,
|
||
L"EDIT",
|
||
text.c_str(),
|
||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_NUMBER,
|
||
x,
|
||
y,
|
||
width,
|
||
height,
|
||
parent,
|
||
reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)),
|
||
nullptr,
|
||
nullptr);
|
||
SendMessageW(control, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
|
||
SendMessageW(control, EM_SETLIMITTEXT, 10, 0);
|
||
g_setting_controls.push_back(control);
|
||
return control;
|
||
}
|
||
|
||
HWND make_button(
|
||
HWND parent,
|
||
int id,
|
||
const std::wstring& text,
|
||
int x,
|
||
int y,
|
||
int width,
|
||
int height
|
||
) {
|
||
HWND control = CreateWindowExW(
|
||
0,
|
||
L"BUTTON",
|
||
text.c_str(),
|
||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
|
||
x,
|
||
y,
|
||
width,
|
||
height,
|
||
parent,
|
||
reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)),
|
||
nullptr,
|
||
nullptr);
|
||
SendMessageW(control, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
|
||
return control;
|
||
}
|
||
|
||
HWND make_search_mode_combo(HWND parent, int x, int y, int width, int height) {
|
||
HWND control = CreateWindowExW(
|
||
0,
|
||
L"COMBOBOX",
|
||
L"",
|
||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST,
|
||
x,
|
||
y,
|
||
width,
|
||
height,
|
||
parent,
|
||
reinterpret_cast<HMENU>(static_cast<INT_PTR>(ID_SEARCH_MODE)),
|
||
nullptr,
|
||
nullptr);
|
||
SendMessageW(control, WM_SETFONT, reinterpret_cast<WPARAM>(g_ui_font), TRUE);
|
||
SendMessageW(control, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Обычный"));
|
||
SendMessageW(control, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Проработка худших"));
|
||
SendMessageW(control, CB_SETCURSEL, 0, 0);
|
||
g_setting_controls.push_back(control);
|
||
return control;
|
||
}
|
||
|
||
void create_controls(HWND window) {
|
||
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(window, L"Szilassi — CUDA-поиск", 18, 14, 430, 32);
|
||
SendMessageW(title, WM_SETFONT, reinterpret_cast<WPARAM>(g_title_font), TRUE);
|
||
make_label(window, L"Папка проекта: " + g_root.wstring(), 20, 50, 980, 24);
|
||
|
||
make_label(window, L"Цепочек (0 = авто)", 22, 86, 138, 22);
|
||
g_cuda_chains = make_number_edit(window, ID_CUDA_CHAINS, L"0", 22, 110, 120, 27);
|
||
|
||
make_label(window, L"Итераций / пакет", 160, 86, 150, 22);
|
||
g_cuda_iters = make_number_edit(window, ID_CUDA_ITERS, L"64", 160, 110, 135, 27);
|
||
|
||
make_label(window, L"Минут", 313, 86, 85, 22);
|
||
g_minutes = make_number_edit(window, ID_MINUTES, L"480", 313, 110, 85, 27);
|
||
|
||
make_label(window, L"Топологии: от", 416, 86, 125, 22);
|
||
g_topology_from = make_number_edit(window, ID_TOPOLOGY_FROM, L"0", 416, 110, 70, 27);
|
||
|
||
make_label(window, L"до", 504, 86, 45, 22);
|
||
g_topology_to = make_number_edit(window, ID_TOPOLOGY_TO, L"58", 504, 110, 70, 27);
|
||
|
||
make_label(window, L"Checkpoint, сек", 592, 86, 135, 22);
|
||
g_checkpoint_seconds =
|
||
make_number_edit(window, ID_CHECKPOINT_SECONDS, L"30", 592, 110, 125, 27);
|
||
|
||
make_label(window, L"Режим поиска", 735, 86, 245, 22);
|
||
g_search_mode = make_search_mode_combo(window, 735, 110, 245, 180);
|
||
|
||
make_label(
|
||
window,
|
||
L"CUDA-устройство выбирается автоматически. Поиск: FP32; финальная проверка кандидатов: DD.",
|
||
22,
|
||
149,
|
||
960,
|
||
24);
|
||
|
||
g_start = make_button(window, ID_START, L"Запустить поиск", 22, 184, 220, 38);
|
||
g_stop = make_button(window, ID_STOP, L"Остановить поиск", 254, 184, 220, 38);
|
||
|
||
g_status = make_label(window, L"Готово", 22, 238, 960, 24);
|
||
|
||
g_log = CreateWindowExW(
|
||
WS_EX_CLIENTEDGE,
|
||
L"EDIT",
|
||
L"",
|
||
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
|
||
18,
|
||
270,
|
||
982,
|
||
400,
|
||
window,
|
||
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);
|
||
|
||
set_search_state(SearchState::idle, L"Готово");
|
||
append_log_direct(L"Интерфейс готов. Существующие checkpoint будут подхвачены автоматически.");
|
||
append_log_direct(L"Исполняемый файл поиска: " + main_exe().wstring());
|
||
}
|
||
|
||
void resize_controls(HWND window) {
|
||
if (g_log == nullptr) {
|
||
return;
|
||
}
|
||
RECT client{};
|
||
GetClientRect(window, &client);
|
||
const int width = client.right - client.left;
|
||
const int height = client.bottom - client.top;
|
||
MoveWindow(g_log, 18, 270, std::max(360, width - 36), std::max(140, height - 290), TRUE);
|
||
}
|
||
|
||
void start_search() {
|
||
if (g_search_state != SearchState::idle) {
|
||
return;
|
||
}
|
||
|
||
int cuda_chains = 0;
|
||
int cuda_iters = 0;
|
||
int minutes = 0;
|
||
int topology_from = 0;
|
||
int topology_to = 0;
|
||
int checkpoint_seconds = 0;
|
||
if (!parse_integer_setting(g_cuda_chains, L"GPU-цепочек", 0, 1048576, cuda_chains) ||
|
||
!parse_integer_setting(g_cuda_iters, L"Итераций / пакет", 1, 64, cuda_iters) ||
|
||
!parse_integer_setting(g_minutes, L"Минут", 0, 525600, minutes) ||
|
||
!parse_integer_setting(g_topology_from, L"Топологии: от", 0, TOPOLOGY_COUNT - 1, topology_from) ||
|
||
!parse_integer_setting(g_topology_to, L"Топологии: до", 0, TOPOLOGY_COUNT - 1, topology_to) ||
|
||
!parse_integer_setting(
|
||
g_checkpoint_seconds,
|
||
L"Checkpoint, сек",
|
||
5,
|
||
3600,
|
||
checkpoint_seconds)) {
|
||
return;
|
||
}
|
||
if (topology_from > topology_to) {
|
||
MessageBoxW(
|
||
g_main_window,
|
||
L"Начало диапазона топологий не может быть больше конца.",
|
||
L"Некорректный диапазон",
|
||
MB_OK | MB_ICONWARNING);
|
||
SetFocus(g_topology_from);
|
||
return;
|
||
}
|
||
|
||
const int seed = make_automatic_seed();
|
||
append_log_direct(L"Seed запуска: " + std::to_wstring(seed));
|
||
|
||
const bool prioritize_worst =
|
||
SendMessageW(g_search_mode, CB_GETCURSEL, 0, 0) == 1;
|
||
append_log_direct(
|
||
prioritize_worst
|
||
? L"Режим поиска: проработка худших вариантов."
|
||
: L"Режим поиска: обычный.");
|
||
|
||
const fs::path stop_path = make_stop_path();
|
||
const std::wstring title =
|
||
L"CUDA-поиск, топологии " + std::to_wstring(topology_from) +
|
||
L"–" + std::to_wstring(topology_to);
|
||
std::vector<std::wstring> args{
|
||
L"--global-search",
|
||
L"--cuda",
|
||
L"--seed", std::to_wstring(seed),
|
||
L"--cuda-chains", std::to_wstring(cuda_chains),
|
||
L"--cuda-iters", std::to_wstring(cuda_iters),
|
||
L"--minutes", std::to_wstring(minutes),
|
||
L"--topology-from", std::to_wstring(topology_from),
|
||
L"--topology-to", std::to_wstring(topology_to),
|
||
L"--checkpoint-seconds", std::to_wstring(checkpoint_seconds),
|
||
L"--stop-file", stop_path.wstring(),
|
||
L"--global-dir", L"results\\search"
|
||
};
|
||
if (prioritize_worst) {
|
||
args.push_back(L"--prioritize-worst");
|
||
}
|
||
start_process(
|
||
title,
|
||
main_exe(),
|
||
args,
|
||
stop_path);
|
||
}
|
||
|
||
void handle_command(int id) {
|
||
switch (id) {
|
||
case ID_START:
|
||
start_search();
|
||
break;
|
||
case ID_STOP:
|
||
request_stop(false);
|
||
break;
|
||
}
|
||
}
|
||
|
||
LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
|
||
switch (message) {
|
||
case WM_CREATE:
|
||
g_main_window = window;
|
||
create_controls(window);
|
||
return 0;
|
||
|
||
case WM_SIZE:
|
||
resize_controls(window);
|
||
return 0;
|
||
|
||
case WM_COMMAND:
|
||
if (HIWORD(wparam) == BN_CLICKED) {
|
||
handle_command(LOWORD(wparam));
|
||
}
|
||
return 0;
|
||
|
||
case WM_APPEND_LOG: {
|
||
auto* text = reinterpret_cast<std::wstring*>(lparam);
|
||
append_log_direct(*text);
|
||
delete text;
|
||
return 0;
|
||
}
|
||
|
||
case WM_PROCESS_DONE: {
|
||
const DWORD exit_code = static_cast<DWORD>(wparam);
|
||
const bool stopped_by_user = g_search_state == SearchState::stopping;
|
||
append_log_direct(L"Процесс завершён, код выхода: " + std::to_wstring(exit_code));
|
||
close_process_handles();
|
||
|
||
if (!g_stop_file.empty()) {
|
||
if (exit_code == 0) {
|
||
std::error_code remove_error;
|
||
fs::remove(g_stop_file, remove_error);
|
||
}
|
||
g_stop_file.clear();
|
||
}
|
||
|
||
if (exit_code == 0 && stopped_by_user) {
|
||
set_search_state(SearchState::idle, L"Поиск остановлен, checkpoint сохранён");
|
||
} else if (exit_code == 0) {
|
||
set_search_state(SearchState::idle, L"Поиск завершён");
|
||
} else {
|
||
set_search_state(
|
||
SearchState::idle,
|
||
L"Поиск завершился с ошибкой, код " + std::to_wstring(exit_code));
|
||
}
|
||
|
||
if (g_close_after_stop) {
|
||
g_close_after_stop = false;
|
||
DestroyWindow(window);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
case WM_QUERYENDSESSION:
|
||
if (g_search_state != SearchState::idle) {
|
||
request_stop(true);
|
||
}
|
||
return TRUE;
|
||
|
||
case WM_CLOSE:
|
||
if (g_search_state == SearchState::running) {
|
||
const int answer = MessageBoxW(
|
||
window,
|
||
L"Поиск ещё выполняется. Штатно остановить его, сохранить checkpoint и закрыть окно?",
|
||
L"Поиск выполняется",
|
||
MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2);
|
||
if (answer == IDYES) {
|
||
request_stop(true);
|
||
}
|
||
return 0;
|
||
}
|
||
if (g_search_state == SearchState::stopping) {
|
||
g_close_after_stop = true;
|
||
SetWindowTextW(g_status, L"Останавливается, сохраняет checkpoint и закроется...");
|
||
return 0;
|
||
}
|
||
DestroyWindow(window);
|
||
return 0;
|
||
|
||
case WM_DESTROY:
|
||
g_main_window = nullptr;
|
||
if (g_search_state == SearchState::idle) {
|
||
close_process_handles();
|
||
}
|
||
if (g_ui_font != nullptr) {
|
||
DeleteObject(g_ui_font);
|
||
g_ui_font = nullptr;
|
||
}
|
||
if (g_title_font != nullptr) {
|
||
DeleteObject(g_title_font);
|
||
g_title_font = nullptr;
|
||
}
|
||
if (g_mono_font != nullptr) {
|
||
DeleteObject(g_mono_font);
|
||
g_mono_font = nullptr;
|
||
}
|
||
PostQuitMessage(0);
|
||
return 0;
|
||
}
|
||
return DefWindowProcW(window, message, wparam, lparam);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show_command) {
|
||
g_root = find_root();
|
||
|
||
WNDCLASSW window_class{};
|
||
window_class.lpfnWndProc = window_proc;
|
||
window_class.hInstance = instance;
|
||
window_class.lpszClassName = L"PolyhedronGuiWindow";
|
||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||
window_class.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
|
||
RegisterClassW(&window_class);
|
||
|
||
HWND window = CreateWindowExW(
|
||
0,
|
||
window_class.lpszClassName,
|
||
L"Szilassi — CUDA-поиск",
|
||
WS_OVERLAPPEDWINDOW,
|
||
CW_USEDEFAULT,
|
||
CW_USEDEFAULT,
|
||
1040,
|
||
740,
|
||
nullptr,
|
||
nullptr,
|
||
instance,
|
||
nullptr);
|
||
if (window == nullptr) {
|
||
return static_cast<int>(GetLastError());
|
||
}
|
||
|
||
ShowWindow(window, show_command);
|
||
UpdateWindow(window);
|
||
|
||
MSG message{};
|
||
while (GetMessageW(&message, nullptr, 0, 0)) {
|
||
TranslateMessage(&message);
|
||
DispatchMessageW(&message);
|
||
}
|
||
return static_cast<int>(message.wParam);
|
||
}
|