diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a04bff8..99a1910 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: run: echo "Runner OS is ${{ runner.os }}" - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install NSIS on Windows if: runner.os == 'Windows' diff --git a/CMakeLists.txt b/CMakeLists.txt index 87fecd8..57788bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.17) -project(PretextView VERSION "1.0.6") +project(PretextView VERSION "1.0.7") +# User-visible version (window title, macOS CFBundleShortVersionString); keep in sync with PretextView.cpp +set(PRETEXTVIEW_APP_VERSION_STRING "1.0.7-beta") option(FORCE_MAC_X86 "Force macOS x86_64 build" OFF) @@ -170,7 +172,6 @@ else() ) endif() -target_compile_definitions(${target_name} PRIVATE PV=${PROJECT_VERSION}) if (CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_definitions(${target_name} PRIVATE DEBUG) target_compile_definitions(copy_texture PRIVATE DEBUG) @@ -304,7 +305,7 @@ file(GLOB aimodel_files "${CMAKE_CURRENT_SOURCE_DIR}/ai_model/*") file(GLOB torch_copy_libs "${TORCH_INSTALL_PREFIX}/lib/*") if (APPLE) execute_process( - COMMAND bash ${CMAKE_SOURCE_DIR}/make_macos_app_plist.sh ${PROJECT_VERSION} + COMMAND bash ${CMAKE_SOURCE_DIR}/make_macos_app_plist.sh "${PRETEXTVIEW_APP_VERSION_STRING}" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} RESULT_VARIABLE result OUTPUT_VARIABLE output @@ -436,7 +437,8 @@ endif() # ==== CPack: installation package ==== include(InstallRequiredSystemLibraries) set(CPACK_PACKAGE_NAME ${target_name}) -set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +# Match installer/DMG name to user-visible version (e.g. PretextViewAI-1.0.7-beta-Darwin-x86_64.dmg) +set(CPACK_PACKAGE_VERSION "${PRETEXTVIEW_APP_VERSION_STRING}") set(CPACK_PACKAGE_DIRECTORY ${CMAKE_BINARY_DIR}) set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/sanger-tol/PretextView") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${ARCH_TYPE}") diff --git a/Info.plist b/Info.plist index b1fbc1d..8fd3f6c 100644 --- a/Info.plist +++ b/Info.plist @@ -13,7 +13,7 @@ CFBundleIconFile icon.icns CFBundleShortVersionString - 1.0.6 + 1.0.7-beta CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/PretextView.cpp b/PretextView.cpp index 83d90f3..8871685 100644 --- a/PretextView.cpp +++ b/PretextView.cpp @@ -26,11 +26,9 @@ SOFTWARE. */ -#define my_String_(x) #x -#define my_String(x) my_String_(x) - -#define PretextView_Version "PretextViewAI Version " my_String(PV) // PV is defined in the CMakeLists.txt -#define PretextView_Title "PretextViewAI " my_String(PV) " - Wellcome Sanger Institute" +#define PretextView_Version_Label "1.0.7-beta" +#define PretextView_Version "PretextViewAI Version " PretextView_Version_Label +#define PretextView_Title "PretextViewAI " PretextView_Version_Label " - Wellcome Sanger Institute" #include // place this before add Header.h to avoid macro conflict @@ -97,6 +95,18 @@ SOFTWARE. #define PV_OPEN_FLAGS (O_WRONLY | O_CREAT | O_TRUNC) #endif +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + #pragma clang diagnostic push #pragma GCC diagnostic ignored "-Wreserved-id-macro" #pragma GCC diagnostic ignored "-Wsign-conversion" @@ -744,12 +754,30 @@ GLFW_Error_Message[512] = {0}; global_variable char -Crash_Report_Path[256] = "pretextview_crash_report.txt"; +Crash_Report_Path[1024] = "pretextview_crash_report.txt"; + +global_variable +char +Loaded_Pretext_Map_FullPath[1024] = {0}; global_variable char Crash_Report_Snapshot[2048] = {0}; +#ifndef _WIN32 +global_variable +char +Crash_Terminal_Log_Buffer[1024 * 1024]; + +global_variable +size_t +Crash_Terminal_Log_Len; +#endif + +global_variable +s32 +Debug_Report_Save_Browser_Open = 0; + global_variable error_context Last_Exception_Context = error_context_none; @@ -874,6 +902,11 @@ GetGlobalModeName(global_mode mode) } } +#ifndef _WIN32 +static void +CopyLogToCrashBuffer(void); +#endif + global_function void CaptureException(error_context ctx, const char *where, const char *message) @@ -931,6 +964,71 @@ UpdateCrashReportSnapshot() (Last_Exception_Message[0] && Last_Exception_Where[0]) ? Last_Exception_Where : "", (Last_Exception_Message[0]) ? ")" : "", (GLFW_Error_Has && GLFW_Error_Message[0]) ? GLFW_Error_Message : ""); +#ifndef _WIN32 + CopyLogToCrashBuffer(); +#endif +} + +global_function +void +ApplyCrashReportPathFromMapPath(const char *full_map_path) +{ + if (!full_map_path || !full_map_path[0]) + { + return; + } + stbsp_snprintf(Loaded_Pretext_Map_FullPath, sizeof(Loaded_Pretext_Map_FullPath), "%s", full_map_path); + + char dir[768]; + char base[256]; +#ifndef _WIN32 + const char *last_slash = strrchr(full_map_path, '/'); +#else + const char *last_slash = strrchr(full_map_path, '/'); + const char *last_bs = strrchr(full_map_path, '\\'); + if (!last_slash || (last_bs && last_bs > last_slash)) + { + last_slash = last_bs; + } +#endif + if (!last_slash) + { + stbsp_snprintf(dir, sizeof(dir), "."); + stbsp_snprintf(base, sizeof(base), "%s", full_map_path); + } + else + { + size_t dlen = (size_t)(last_slash - full_map_path); + if (dlen >= sizeof(dir)) + { + dlen = sizeof(dir) - 1; + } + memcpy(dir, full_map_path, dlen); + dir[dlen] = '\0'; + stbsp_snprintf(base, sizeof(base), "%s", last_slash + 1); + } + char prefix[256]; + stbsp_snprintf(prefix, sizeof(prefix), "%s", base); + char *dot = strrchr(prefix, '.'); + if (dot) + { + *dot = '\0'; + } +#ifndef _WIN32 + stbsp_snprintf( + Crash_Report_Path, + sizeof(Crash_Report_Path), + "%s/%s_crash_report.txt", + dir, + prefix[0] ? prefix : "pretextview"); +#else + stbsp_snprintf( + Crash_Report_Path, + sizeof(Crash_Report_Path), + "%s\\%s_crash_report.txt", + dir, + prefix[0] ? prefix : "pretextview"); +#endif } global_function @@ -968,6 +1066,16 @@ CrashSignalHandler(int sig) write_all(fd, sigName); write_all(fd, newline); write_all(fd, Crash_Report_Snapshot); +#ifndef _WIN32 + { + const char *sep = "\n--- Terminal log ---\n"; + write_all(fd, sep); + if (Crash_Terminal_Log_Len > 0) + { + (void)PV_WRITE(fd, Crash_Terminal_Log_Buffer, (unsigned)Crash_Terminal_Log_Len); + } + } +#endif PV_CLOSE(fd); } @@ -1215,6 +1323,7 @@ struct file_browser loadBrowser; struct file_browser saveAGPBrowser; struct file_browser saveEditsBrowser; struct file_browser saveClipboardBrowser; +struct file_browser saveDebugReportBrowser; struct file_browser loadAGPBrowser; @@ -1557,6 +1666,10 @@ global_variable char Clipboard_Save_Name_Buffer[1024] = "clipboard.txt"; +global_variable +char +Debug_Report_Save_Name_Buffer[1024] = "debug_report.txt"; + global_function void SetSaveStateNameBuffer(char *name) @@ -1591,6 +1704,29 @@ SetSaveStateNameBuffer(char *name) while (*name) Clipboard_Save_Name_Buffer[ptr++] = *name++; Clipboard_Save_Name_Buffer[ptr] = 0; + ptr = ptr1; + name = (char *)"_debug_report.txt"; + while (*name) Debug_Report_Save_Name_Buffer[ptr++] = *name++; + Debug_Report_Save_Name_Buffer[ptr] = 0; +} + + +static char * +GetSaveNameBufferForMode(u08 save) +{ + switch (save) + { + case 2: + return AGP_Name_Buffer; + case 3: + return Edits_Name_Buffer; + case 4: + return Clipboard_Save_Name_Buffer; + case 5: + return Debug_Report_Save_Name_Buffer; + default: + return Save_State_Name_Buffer; + } } @@ -1601,6 +1737,7 @@ SetSaveStateNameBuffer(char *name) 2 agp state 3 edits save 4 clipboard save + 5 debug report save */ global_function u08 @@ -1663,8 +1800,16 @@ FileBrowserRun(const char *name, struct file_browser *browser, struct nk_context nk_menubar_end(ctx); ctx->style.window.spacing.x = spacing_x; - /* window layout */ - f32 endSpace = save ? 100.0f : 0; // Resolved: end space is clipped as the space is not enough + /* window layout — reserve footer height for save dialogs (filename row clips if too small) */ + f32 endSpace = 0; + if (save == 5) + { + endSpace = Screen_Scale.y * 158.0f; + } + else if (save) + { + endSpace = Screen_Scale.y * 108.0f; + } total_space = nk_window_get_content_region(ctx); nk_layout_row(ctx, NK_DYNAMIC, total_space.h - endSpace, 2, ratio); nk_group_begin(ctx, "Special", NK_WINDOW_NO_SCROLLBAR); @@ -1744,7 +1889,7 @@ FileBrowserRun(const char *name, struct file_browser *browser, struct nk_context { if (save) { - char *nameBuffer = save == 2 ? AGP_Name_Buffer : (save == 3 ? Edits_Name_Buffer : (save == 4 ? Clipboard_Save_Name_Buffer : Save_State_Name_Buffer)); + char *nameBuffer = GetSaveNameBufferForMode(save); strncpy(nameBuffer, browser->files[fileIndex], 1024); } else @@ -1783,28 +1928,73 @@ FileBrowserRun(const char *name, struct file_browser *browser, struct nk_context nk_layout_row(ctx, NK_DYNAMIC, endSpace - 5.0f, 1, ratio + 1); nk_group_begin(ctx, "File", NK_WINDOW_NO_SCROLLBAR); { - f32 fileRatio[] = {0.8f, 0.1f, NK_UNDEFINED}; - f32 fileRatio2[] = {0.45f, 0.1f, 0.18f, 0.17f, NK_UNDEFINED}; - nk_layout_row(ctx, NK_DYNAMIC, Screen_Scale.y * 35.0f, save == 2 ? 5 : 3, save == 2 ? fileRatio2 : fileRatio); - - char *nameBuffer = save == 2 ? AGP_Name_Buffer : (save == 3 ? Edits_Name_Buffer : (save == 4 ? Clipboard_Save_Name_Buffer : Save_State_Name_Buffer)); - u08 saveViaEnter = (nk_edit_string_zero_terminated(ctx, NK_EDIT_FIELD | NK_EDIT_SIG_ENTER, nameBuffer, 1024, 0) & NK_EDIT_COMMITED) ? 1 : 0; - static u08 overwrite = 0; - overwrite = (u08)nk_check_label(ctx, "Override", overwrite); - - static u08 singletons = 0; - if (save == 2) singletons = (u08)nk_check_label(ctx, "Format Singletons", singletons); - static u08 preserveOrder = 0; - if (save == 2) preserveOrder = (u08)nk_check_label(ctx, "Preserve Order", preserveOrder); - if (nk_button_label(ctx, "Save") || saveViaEnter) + if (save == 5) + { + nk_layout_row_dynamic(ctx, Screen_Scale.y * 42.0f, 1); + nk_label_wrap( + ctx, + "Use the folder list above to choose a directory (click folders or the path bar). " + "Then type the report file name below and click Save. " + "The full path is: current folder + file name."); + nk_layout_row_dynamic(ctx, Screen_Scale.y * 28.0f, 1); + nk_label(ctx, "File name:", NK_TEXT_LEFT); + nk_layout_row_dynamic(ctx, Screen_Scale.y * 32.0f, 1); + char *nameBuffer = GetSaveNameBufferForMode(5); + u08 saveViaEnter = + (nk_edit_string_zero_terminated( + ctx, + NK_EDIT_FIELD | NK_EDIT_SIG_ENTER, + nameBuffer, + 1024, + 0) + & NK_EDIT_COMMITED) + ? 1 + : 0; + nk_layout_row_dynamic(ctx, Screen_Scale.y * 34.0f, 2); + overwrite = (u08)nk_check_label(ctx, "Override", overwrite); + if (nk_button_label(ctx, "Save") || saveViaEnter) + { + strncpy(browser->file, browser->directory, MAX_PATH_LEN); + size_t n = strlen(browser->file); + strncpy(browser->file + n, nameBuffer, MAX_PATH_LEN - n); + ret = 1 | (overwrite ? 2 : 0); + } + } + else { - strncpy(browser->file, browser->directory, MAX_PATH_LEN); - size_t n = strlen(browser->file); - char *nameBuffer = save == 2 ? AGP_Name_Buffer : (save == 3 ? Edits_Name_Buffer : (save == 4 ? Clipboard_Save_Name_Buffer : Save_State_Name_Buffer)); - strncpy(browser->file + n, nameBuffer, MAX_PATH_LEN - n); - ret = 1 | (overwrite ? 2 : 0) | (singletons ? 4 : 0) | (preserveOrder ? 8 : 0); + f32 fileRatio[] = {0.8f, 0.1f, NK_UNDEFINED}; + f32 fileRatio2[] = {0.45f, 0.1f, 0.18f, 0.17f, NK_UNDEFINED}; + nk_layout_row(ctx, NK_DYNAMIC, Screen_Scale.y * 35.0f, save == 2 ? 5 : 3, save == 2 ? fileRatio2 : fileRatio); + + char *nameBuffer = GetSaveNameBufferForMode(save); + u08 saveViaEnter = + (nk_edit_string_zero_terminated(ctx, NK_EDIT_FIELD | NK_EDIT_SIG_ENTER, nameBuffer, 1024, 0) & NK_EDIT_COMMITED) + ? 1 + : 0; + + overwrite = (u08)nk_check_label(ctx, "Override", overwrite); + + static u08 singletons = 0; + if (save == 2) + { + singletons = (u08)nk_check_label(ctx, "Format Singletons", singletons); + } + static u08 preserveOrder = 0; + if (save == 2) + { + preserveOrder = (u08)nk_check_label(ctx, "Preserve Order", preserveOrder); + } + + if (nk_button_label(ctx, "Save") || saveViaEnter) + { + strncpy(browser->file, browser->directory, MAX_PATH_LEN); + size_t n = strlen(browser->file); + nameBuffer = GetSaveNameBufferForMode(save); + strncpy(browser->file + n, nameBuffer, MAX_PATH_LEN - n); + ret = 1 | (overwrite ? 2 : 0) | (singletons ? 4 : 0) | (preserveOrder ? 8 : 0); + } } nk_group_end(ctx); @@ -2091,9 +2281,7 @@ UpdateContigsFromMapState() // Reading updates contigs from the state of the ma // `Number_of_Original_Contigs` is 218.) ForLoop(Number_of_Pixels_1D - 1) { - // Ensure that contigPtr does not exceed the value of the maximum - // number of contigs. - if (contigPtr >= Contigs->numberOfContigs) + if (contigPtr >= Contigs->contigs_arr_capacity) break; ++length; // current fragment length @@ -2201,9 +2389,11 @@ UpdateContigsFromMapState() // Reading updates contigs from the state of the ma lastCoord = coord; } - if (contigPtr < Contigs->numberOfContigs) - // Update the fragment information of the last contig. + // Trailing fragment: numberOfContigs is still the previous pass here (assigned below). + // Comparing contigPtr to it skipped the last block when contigPtr equalled that stale count. + if (length > 0 && contigPtr < Contigs->contigs_arr_capacity) { + // Update the fragment information of the last contig. u32 lastId_original_contig_base = GetOriginalContigBaseId(lastId_original_contig); (Original_Contigs + lastId_original_contig_base) ->contigMapPixels[(Original_Contigs + lastId_original_contig_base)->nContigs++] = pixelIdx - 1 - (length >> 1); @@ -3419,7 +3609,7 @@ Mouse(GLFWwindow* window, s32 button, s32 action, s32 mods) { InvertMap(Edit_Pixels.pixels.x, Edit_Pixels.pixels.y); Global_Edit_Invert_Flag = !Global_Edit_Invert_Flag; - UpdateContigsFromMapState(); + // UpdateContigsFromMapState() already invoked from InvertMap() Redisplay = 1; } @@ -3594,15 +3784,228 @@ global_variable nk_color Theme_Colour; +#ifndef _WIN32 +global_variable +std::mutex +Terminal_Log_Mutex; + +global_variable +std::string +Terminal_Log_Text; + +global_variable +std::atomic +Terminal_Log_Generation{}; + +global_function +void +Terminal_Log_AppendChunk(const char *data, size_t n) +{ + if (!data || n == 0) + { + return; + } + std::lock_guard lock(Terminal_Log_Mutex); + Terminal_Log_Text.append(data, n); + const size_t cap = 1024 * 1024; + if (Terminal_Log_Text.size() > cap) + { + Terminal_Log_Text.erase(0, Terminal_Log_Text.size() - cap + cap / 4); + size_t cut = Terminal_Log_Text.find('\n'); + if (cut != std::string::npos && cut < 4096) + { + Terminal_Log_Text.erase(0, cut + 1); + } + } + Terminal_Log_Generation.fetch_add(1, std::memory_order_relaxed); +} + +global_function +void +Terminal_Log_ReaderLoop(int read_fd, int tty_fd) +{ + char buf[4096]; + for (;;) + { + ssize_t n = read(read_fd, buf, sizeof buf); + if (n < 0) + { + if (errno == EINTR) + { + continue; + } + break; + } + if (n == 0) + { + break; + } + if (tty_fd >= 0) + { + (void)write(tty_fd, buf, (size_t)n); + } + Terminal_Log_AppendChunk(buf, (size_t)n); + } +} + +global_function +void +Terminal_Log_Init(void) +{ + /* Drain libc buffers before replacing fds so nothing is stuck for the old destination. */ + (void)fflush(stdout); + (void)fflush(stderr); + + int fds[2]; + if (pipe(fds) != 0) + { + return; + } +#ifdef F_SETPIPE_SZ + (void)fcntl(fds[1], F_SETPIPE_SZ, 1024 * 1024); +#endif + int tty = dup(STDOUT_FILENO); + if (tty < 0) + { + close(fds[0]); + close(fds[1]); + return; + } + if (dup2(fds[1], STDOUT_FILENO) < 0 || dup2(fds[1], STDERR_FILENO) < 0) + { + close(tty); + close(fds[0]); + close(fds[1]); + return; + } + close(fds[1]); + + /* + * After dup2, stdout is a pipe; libc may use full buffering unless we force no buffering. + * Line-buffering on non-tty pipes is unreliable — use unbuffered so every write reaches the reader. + */ + clearerr(stdout); + clearerr(stderr); + (void)setvbuf(stdout, NULL, _IONBF, 0); + (void)setvbuf(stderr, NULL, _IONBF, 0); + + std::cout.clear(); + std::cerr.clear(); + std::cout.setf(std::ios::unitbuf); + std::cerr.setf(std::ios::unitbuf); + + /* Python uses block-buffered stdout when it is a pipe unless this is set before interpreter start. */ + (void)setenv("PYTHONUNBUFFERED", "1", 1); + + int r = fds[0]; + int t = tty; + std::thread([r, t]() { Terminal_Log_ReaderLoop(r, t); }).detach(); +} +#else +global_function +void +Terminal_Log_Init(void) +{ +} +#endif + +#ifndef _WIN32 +static void +CopyLogToCrashBuffer(void) +{ + std::lock_guard lock(Terminal_Log_Mutex); + size_t n = Terminal_Log_Text.size(); + if (n >= sizeof(Crash_Terminal_Log_Buffer)) + { + n = sizeof(Crash_Terminal_Log_Buffer) - 1; + } + memcpy(Crash_Terminal_Log_Buffer, Terminal_Log_Text.data(), n); + Crash_Terminal_Log_Len = n; +} +#endif + +global_function +void +WriteDebugReportToFile(const char *path) +{ + if (!path || !path[0]) + { + return; + } + UpdateCrashReportSnapshot(); + FILE *f = fopen(path, "wb"); + if (!f) + { + fmt::print(stderr, "[Debug report] could not open {} for writing\n", path); + return; + } + fputs("PretextView debug report\n\n", f); + fputs(Crash_Report_Snapshot, f); +#ifndef _WIN32 + fputs("\n--- Terminal log ---\n", f); + { + std::lock_guard lock(Terminal_Log_Mutex); + if (!Terminal_Log_Text.empty()) + { + fwrite(Terminal_Log_Text.data(), 1, Terminal_Log_Text.size(), f); + } + } +#endif + fclose(f); + fmt::print(stdout, "[Debug report] saved to %s\n", path); +} + +global_function +void +PrimeDebugReportSaveBrowserDirectory(void) +{ + if (!Loaded_Pretext_Map_FullPath[0]) + { + return; + } + char dir[1024]; + stbsp_snprintf(dir, sizeof(dir), "%s", Loaded_Pretext_Map_FullPath); +#ifndef _WIN32 + char *slash = strrchr(dir, '/'); +#else + char *slash = strrchr(dir, '/'); + char *bs = strrchr(dir, '\\'); + if (!slash || (bs && bs > slash)) + { + slash = bs; + } +#endif + if (slash) + { + *slash = '\0'; + FileBrowserReloadDirectoryContent(&saveDebugReportBrowser, dir); + } +} + global_function void DebugWindowRun(struct nk_context *ctx) { + static u08 prev_debug_ui_on = 0; + static int save_report_click_suppress_frames = 0; + if (!Debug_UI_On) { + prev_debug_ui_on = 0; return; } + /* Opening from the menu can leave the same click/focus to hit "Save report..." first row — skip that for a few frames. */ + if (!prev_debug_ui_on) + { + save_report_click_suppress_frames = 3; + } + prev_debug_ui_on = 1; + if (save_report_click_suppress_frames > 0) + { + --save_report_click_suppress_frames; + } + const char *name = "Debug Report"; struct nk_window *window = nk_window_find(ctx, name); if (window && (window->flags & NK_WINDOW_HIDDEN)) @@ -3613,135 +4016,125 @@ DebugWindowRun(struct nk_context *ctx) if (nk_begin( ctx, name, - nk_rect(Screen_Scale.x * 10, Screen_Scale.y * 620, Screen_Scale.x * 420, Screen_Scale.y * 360), + nk_rect(Screen_Scale.x * 10, Screen_Scale.y * 80, Screen_Scale.x * 720, Screen_Scale.y * 520), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_SCALABLE)) { - char buff[256]; - nk_layout_row_dynamic(ctx, Screen_Scale.y * 20.0f, 1); - - stbsp_snprintf(buff, sizeof(buff), "Mode: %s", GetGlobalModeName(Global_Mode)); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf(buff, sizeof(buff), "UI_On: %u Redisplay: %u", UI_On, Redisplay); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf(buff, sizeof(buff), "Loading: %u File_Loaded: %u", Loading, File_Loaded); - nk_label(ctx, buff, NK_TEXT_LEFT); - - const char *currentContext = GetCurrentErrorContextName(); - const char *currentWhere = GetCurrentErrorContextWhere(); - if (currentWhere && currentWhere[0]) + struct nk_rect content = nk_window_get_content_region(ctx); + f32 btn_h = Screen_Scale.y * 30.0f; + f32 log_h = content.h - btn_h - ctx->style.window.spacing.y; + if (log_h < Screen_Scale.y * 120.0f) { - stbsp_snprintf(buff, sizeof(buff), "Current context: %s (%s)", currentContext, currentWhere); + log_h = Screen_Scale.y * 120.0f; } - else - { - stbsp_snprintf(buff, sizeof(buff), "Current context: %s", currentContext); - } - nk_label(ctx, buff, NK_TEXT_LEFT); - const char *lastError = GetLastErrorMessage(); - const char *lastErrorCtx = GetLastErrorContextName(); - const char *lastErrorWhere = GetLastErrorContextWhere(); - if (lastError && lastError[0]) + nk_layout_row_dynamic(ctx, btn_h, 1); + nk_style_push_font(ctx, &NK_Font_Browser->handle); + nk_button_push_behavior(ctx, NK_BUTTON_DEFAULT); { - if (lastErrorWhere && lastErrorWhere[0]) + /* One activation per click: nk_button_label can stay true for multiple frames while held. */ + static int save_report_button_was_down = 0; + const int down = nk_button_label(ctx, "Save report to file..."); + if (down && !save_report_button_was_down && save_report_click_suppress_frames == 0) { - stbsp_snprintf(buff, sizeof(buff), "Last error: %s (%s, %s)", lastError, lastErrorCtx, lastErrorWhere); + PrimeDebugReportSaveBrowserDirectory(); + Debug_Report_Save_Browser_Open = 1; } - else - { - stbsp_snprintf(buff, sizeof(buff), "Last error: %s (%s)", lastError, lastErrorCtx); - } - } - else - { - stbsp_snprintf(buff, sizeof(buff), "Last error: "); + save_report_button_was_down = down; } - nk_label(ctx, buff, NK_TEXT_LEFT); + nk_button_pop_behavior(ctx); - if (Last_Exception_Message[0]) + nk_layout_row_dynamic(ctx, log_h, 1); +#ifndef _WIN32 { - const char *excCtx = GetErrorContextName(Last_Exception_Context); - if (Last_Exception_Where[0]) + /* Throttled snapshot + virtualized rows: avoid memcpy and nk_edit_string on huge buffers every frame. */ + static char term_display[512 * 1024]; + static std::vector line_starts; + static u64 last_synced_gen = ~(u64)0; + static f64 last_sync_time = 0.0; + static s32 cached_disp_len = 0; + + const f64 kMinSyncInterval = 0.05; + u64 gen_now = Terminal_Log_Generation.load(std::memory_order_acquire); + f64 now = GetTime(); + if (gen_now != last_synced_gen && + (now - last_sync_time >= kMinSyncInterval || last_synced_gen == ~(u64)0)) + { + last_sync_time = now; + static const char pfx[] = "... (earlier lines omitted)\n"; + const size_t plen = sizeof(pfx) - 1; + const size_t max_body = sizeof(term_display) - plen - 1; + + std::lock_guard lock(Terminal_Log_Mutex); + const std::string &src = Terminal_Log_Text; + size_t body_len = src.size(); + size_t body_off = 0; + if (body_len > max_body) + { + memcpy(term_display, pfx, plen); + body_off = body_len - (max_body - plen); + body_len = max_body - plen; + memcpy(term_display + plen, src.data() + body_off, body_len); + cached_disp_len = (s32)(plen + body_len); + } + else + { + memcpy(term_display, src.data(), body_len); + cached_disp_len = (s32)body_len; + } + term_display[cached_disp_len] = '\0'; + + line_starts.clear(); + line_starts.push_back(0); + for (s32 i = 0; i < cached_disp_len; ++i) + { + if (term_display[i] == '\n') + { + line_starts.push_back((u32)(i + 1)); + } + } + last_synced_gen = Terminal_Log_Generation.load(std::memory_order_acquire); + } + + int row_h = (int)(NK_Font_Browser->handle.height + ctx->style.text.padding.y + 2.0f); + if (row_h < 12) { - stbsp_snprintf(buff, sizeof(buff), "Last exception: %s (%s, %s)", Last_Exception_Message, excCtx, Last_Exception_Where); + row_h = 14; } - else + int nlines = (int)line_starts.size(); + if (nlines < 1) { - stbsp_snprintf(buff, sizeof(buff), "Last exception: %s (%s)", Last_Exception_Message, excCtx); + nlines = 1; } - } - else - { - stbsp_snprintf(buff, sizeof(buff), "Last exception: "); - } - nk_label(ctx, buff, NK_TEXT_LEFT); - if (GLFW_Error_Has && GLFW_Error_Message[0]) - { - stbsp_snprintf(buff, sizeof(buff), "GLFW error: %s", GLFW_Error_Message); + struct nk_list_view lv; + if (nk_list_view_begin(ctx, &lv, "PVDebugTerm", NK_WINDOW_BORDER, row_h, nlines)) + { + for (int row = lv.begin; row < lv.end; ++row) + { + nk_layout_row_dynamic(ctx, (f32)row_h, 1); + u32 a = line_starts[(u32)row]; + u32 b = ((u32)row + 1 < line_starts.size()) ? line_starts[(u32)row + 1] : (u32)cached_disp_len; + int line_len = (int)(b - a); + if (line_len > 0 && term_display[b - 1] == '\n') + { + line_len--; + } + if (line_len <= 0) + { + nk_label(ctx, " ", NK_TEXT_LEFT); + } + else + { + nk_text(ctx, term_display + a, line_len, NK_TEXT_LEFT); + } + } + nk_list_view_end(&lv); + } } - else - { - stbsp_snprintf(buff, sizeof(buff), "GLFW error: "); - } - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf(buff, sizeof(buff), "Crash report file: %s", Crash_Report_Path); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf(buff, sizeof(buff), "AutoSort: %u AutoCut: %u", auto_sort_state, auto_cut_state); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf( - buff, - sizeof(buff), - "Edit: editing=%u selecting=%u snap=%u scaffSelect=%u", - Edit_Pixels.editing, - Edit_Pixels.selecting, - Edit_Pixels.snap, - Edit_Pixels.scaffSelecting); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf( - buff, - sizeof(buff), - "Pixels: x=%u y=%u", - Edit_Pixels.pixels.x, - Edit_Pixels.pixels.y); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf( - buff, - sizeof(buff), - "Camera: x=%.3f y=%.3f z=%.3f", - Camera_Position.x, - Camera_Position.y, - Camera_Position.z); - nk_label(ctx, buff, NK_TEXT_LEFT); - - u32 contigCount = (File_Loaded && Contigs) ? Contigs->numberOfContigs : 0; - u32 edits = (Map_Editor) ? Map_Editor->nEdits : 0; - u32 undone = (Map_Editor) ? Map_Editor->nUndone : 0; - u32 waypoints = (Waypoint_Editor) ? Waypoint_Editor->nWaypointsActive : 0; - - stbsp_snprintf(buff, sizeof(buff), "Pixels1D: %u", Number_of_Pixels_1D); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf( - buff, - sizeof(buff), - "Original contigs: %u Editable contigs: %u", - Number_of_Original_Contigs, - contigCount); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf(buff, sizeof(buff), "Edits: %u Undone: %u", edits, undone); - nk_label(ctx, buff, NK_TEXT_LEFT); - - stbsp_snprintf(buff, sizeof(buff), "Waypoints: %u", waypoints); - nk_label(ctx, buff, NK_TEXT_LEFT); +#else + nk_label(ctx, "Terminal log is not captured on Windows.", NK_TEXT_LEFT); +#endif + nk_style_pop_font(ctx); } nk_end(ctx); @@ -3936,6 +4329,14 @@ AddExtension(extension_node *node) } } +global_function +void +EnsureGridDataBuffersForContigCount(u32 nContigs); + +global_function +void +EnsureContigColourBarBuffersForContigCount(u32 nContigs); + global_function void Render() { @@ -4187,6 +4588,8 @@ Render() { // Grid if (File_Loaded && Grid->on) { + EnsureGridDataBuffersForContigCount(Contigs->numberOfContigs); + glUseProgram(Flat_Shader->shaderProgram); glUniform4fv(Flat_Shader->colorLocation, 1, (GLfloat *)&Grid->bg); @@ -4291,6 +4694,8 @@ Render() { // Contig Id Bars if (File_Loaded && Contig_Ids->on) { + EnsureContigColourBarBuffersForContigCount(Contigs->numberOfContigs); + glUseProgram(Flat_Shader->shaderProgram); glUniform4fv(Flat_Shader->colorLocation, 1, (GLfloat *)&Grid->bg); @@ -5768,6 +6173,8 @@ Render() { f32 min = my_Min(Edit_Pixels.worldCoords.x, Edit_Pixels.worldCoords.y); f32 max = my_Max(Edit_Pixels.worldCoords.x, Edit_Pixels.worldCoords.y); + const f32 mapEdge = 0.5f; + if (Global_Edit_Invert_Flag) // draw the two arrows { f32 spacing = 0.002f / Camera_Position.z; @@ -5802,41 +6209,51 @@ Render() { color[3] = alpha; glUniform4fv(Flat_Shader->colorLocation, 1, color); + f32 yTopInner = -min; + f32 yTopEdge = mapEdge; + + f32 xLeftOuter = min; + + f32 yBotInner = -max; + f32 yBotEdge = -mapEdge; + + f32 xRightInner = max; + // upper vertical part - vert[0].x = ModelXToScreen(min); vert[0].y = ModelYToScreen(0.5f); - vert[1].x = ModelXToScreen(min); vert[1].y = ModelYToScreen(-min); - vert[2].x = ModelXToScreen(max); vert[2].y = ModelYToScreen(-min); - vert[3].x = ModelXToScreen(max); vert[3].y = ModelYToScreen(0.5f); + vert[0].x = ModelXToScreen(min); vert[0].y = ModelYToScreen(yTopEdge); + vert[1].x = ModelXToScreen(min); vert[1].y = ModelYToScreen(yTopInner); + vert[2].x = ModelXToScreen(max); vert[2].y = ModelYToScreen(yTopInner); + vert[3].x = ModelXToScreen(max); vert[3].y = ModelYToScreen(yTopEdge); glBindBuffer(GL_ARRAY_BUFFER, Edit_Mode_Data->vbos[ptr]); glBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(vertex), vert); glBindVertexArray(Edit_Mode_Data->vaos[ptr++]); glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_SHORT, NULL); // left horizontal part - vert[0].x = ModelXToScreen(-0.5f); vert[0].y = ModelYToScreen(-min); - vert[1].x = ModelXToScreen(-0.5f); vert[1].y = ModelYToScreen(-max); - vert[2].x = ModelXToScreen(min); vert[2].y = ModelYToScreen(-max); - vert[3].x = ModelXToScreen(min); vert[3].y = ModelYToScreen(-min); + vert[0].x = ModelXToScreen(-mapEdge); vert[0].y = ModelYToScreen(-min); + vert[1].x = ModelXToScreen(-mapEdge); vert[1].y = ModelYToScreen(-max); + vert[2].x = ModelXToScreen(xLeftOuter); vert[2].y = ModelYToScreen(-max); + vert[3].x = ModelXToScreen(xLeftOuter); vert[3].y = ModelYToScreen(-min); glBindBuffer(GL_ARRAY_BUFFER, Edit_Mode_Data->vbos[ptr]); glBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(vertex), vert); glBindVertexArray(Edit_Mode_Data->vaos[ptr++]); glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_SHORT, NULL); // lower vertical part - vert[0].x = ModelXToScreen(min); vert[0].y = ModelYToScreen(-max); - vert[1].x = ModelXToScreen(min); vert[1].y = ModelYToScreen(-0.5f); - vert[2].x = ModelXToScreen(max); vert[2].y = ModelYToScreen(-0.5f); - vert[3].x = ModelXToScreen(max); vert[3].y = ModelYToScreen(-max); + vert[0].x = ModelXToScreen(min); vert[0].y = ModelYToScreen(yBotInner); + vert[1].x = ModelXToScreen(min); vert[1].y = ModelYToScreen(yBotEdge); + vert[2].x = ModelXToScreen(max); vert[2].y = ModelYToScreen(yBotEdge); + vert[3].x = ModelXToScreen(max); vert[3].y = ModelYToScreen(yBotInner); glBindBuffer(GL_ARRAY_BUFFER, Edit_Mode_Data->vbos[ptr]); glBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(vertex), vert); glBindVertexArray(Edit_Mode_Data->vaos[ptr++]); glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_SHORT, NULL); // right horizontal part - vert[0].x = ModelXToScreen(max); vert[0].y = ModelYToScreen(-min); - vert[1].x = ModelXToScreen(max); vert[1].y = ModelYToScreen(-max); - vert[2].x = ModelXToScreen(0.5f); vert[2].y = ModelYToScreen(-max); - vert[3].x = ModelXToScreen(0.5f); vert[3].y = ModelYToScreen(-min); + vert[0].x = ModelXToScreen(xRightInner); vert[0].y = ModelYToScreen(-min); + vert[1].x = ModelXToScreen(xRightInner); vert[1].y = ModelYToScreen(-max); + vert[2].x = ModelXToScreen(mapEdge); vert[2].y = ModelYToScreen(-max); + vert[3].x = ModelXToScreen(mapEdge); vert[3].y = ModelYToScreen(-min); glBindBuffer(GL_ARRAY_BUFFER, Edit_Mode_Data->vbos[ptr]); glBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(vertex), vert); glBindVertexArray(Edit_Mode_Data->vaos[ptr++]); @@ -6327,6 +6744,155 @@ global_variable GLuint Quad_EBO; +static u08 Grid_Data_GL_heap; +static u08 Contig_ColourBar_Data_GL_heap; + +global_function +void +EnsureGridDataBuffersForContigCount(u32 nContigs) +{ + if (!File_Loaded || !Grid_Data || !Flat_Shader) + { + return; + } + u32 need = 2 * (nContigs + 1) + 4; // small slack for driver / counting edge cases + if (need < 4) + { + need = 4; + } + if (Grid_Data->nBuffers >= need) + { + return; + } + + GLuint posAttrib = (GLuint)glGetAttribLocation(Flat_Shader->shaderProgram, "position"); + glUseProgram(Flat_Shader->shaderProgram); + + if (Grid_Data->nBuffers > 0 && Grid_Data->vaos && Grid_Data->vbos) + { + glDeleteVertexArrays((GLsizei)Grid_Data->nBuffers, Grid_Data->vaos); + glDeleteBuffers((GLsizei)Grid_Data->nBuffers, Grid_Data->vbos); + if (Grid_Data_GL_heap) + { + free(Grid_Data->vaos); + free(Grid_Data->vbos); + Grid_Data_GL_heap = 0; + } + } + + Grid_Data->vaos = (GLuint *)malloc((size_t)need * sizeof(GLuint)); + Grid_Data->vbos = (GLuint *)malloc((size_t)need * sizeof(GLuint)); + if (!Grid_Data->vaos || !Grid_Data->vbos) + { + if (Grid_Data->vaos) + { + free(Grid_Data->vaos); + } + if (Grid_Data->vbos) + { + free(Grid_Data->vbos); + } + Grid_Data->vaos = NULL; + Grid_Data->vbos = NULL; + Grid_Data->nBuffers = 0; + return; + } + Grid_Data_GL_heap = 1; + + ForLoop(need) + { + glGenVertexArrays(1, Grid_Data->vaos + index); + glBindVertexArray(Grid_Data->vaos[index]); + + glGenBuffers(1, Grid_Data->vbos + index); + glBindBuffer(GL_ARRAY_BUFFER, Grid_Data->vbos[index]); + glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(vertex), NULL, GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(posAttrib); + glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), 0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Quad_EBO); + } + + Grid_Data->nBuffers = need; + + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} +/*function to ensure the contig colour bar data buffers are for the correct number of contigs, each edit of the contig will call this function --yy5*/ +global_function +void +EnsureContigColourBarBuffersForContigCount(u32 nContigs) +{ + if (!File_Loaded || !Contig_ColourBar_Data || !Flat_Shader) + { + return; + } + u32 need = nContigs + 4; + if (need < 4) + { + need = 4; + } + if (Contig_ColourBar_Data->nBuffers >= need) + { + return; + } + + GLuint posAttrib = (GLuint)glGetAttribLocation(Flat_Shader->shaderProgram, "position"); + glUseProgram(Flat_Shader->shaderProgram); + + if (Contig_ColourBar_Data->nBuffers > 0 && Contig_ColourBar_Data->vaos && Contig_ColourBar_Data->vbos) + { + glDeleteVertexArrays((GLsizei)Contig_ColourBar_Data->nBuffers, Contig_ColourBar_Data->vaos); + glDeleteBuffers((GLsizei)Contig_ColourBar_Data->nBuffers, Contig_ColourBar_Data->vbos); + if (Contig_ColourBar_Data_GL_heap) + { + free(Contig_ColourBar_Data->vaos); + free(Contig_ColourBar_Data->vbos); + Contig_ColourBar_Data_GL_heap = 0; + } + } + + Contig_ColourBar_Data->vaos = (GLuint *)malloc((size_t)need * sizeof(GLuint)); + Contig_ColourBar_Data->vbos = (GLuint *)malloc((size_t)need * sizeof(GLuint)); + if (!Contig_ColourBar_Data->vaos || !Contig_ColourBar_Data->vbos) + { + if (Contig_ColourBar_Data->vaos) + { + free(Contig_ColourBar_Data->vaos); + } + if (Contig_ColourBar_Data->vbos) + { + free(Contig_ColourBar_Data->vbos); + } + Contig_ColourBar_Data->vaos = NULL; + Contig_ColourBar_Data->vbos = NULL; + Contig_ColourBar_Data->nBuffers = 0; + return; + } + Contig_ColourBar_Data_GL_heap = 1; + + ForLoop(need) + { + glGenVertexArrays(1, Contig_ColourBar_Data->vaos + index); + glBindVertexArray(Contig_ColourBar_Data->vaos[index]); + + glGenBuffers(1, Contig_ColourBar_Data->vbos + index); + glBindBuffer(GL_ARRAY_BUFFER, Contig_ColourBar_Data->vbos[index]); + glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(vertex), NULL, GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(posAttrib); + glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), 0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Quad_EBO); + } + + Contig_ColourBar_Data->nBuffers = need; + + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + enum load_file_result { @@ -6593,6 +7159,12 @@ LoadFile(const char *filePath, memory_arena *arena, char **fileName, u64 *header glDeleteVertexArrays((GLsizei)Grid_Data->nBuffers, Grid_Data->vaos); glDeleteBuffers((GLsizei)Grid_Data->nBuffers, Grid_Data->vbos); + if (Grid_Data_GL_heap) + { + free(Grid_Data->vaos); + free(Grid_Data->vbos); + Grid_Data_GL_heap = 0; + } glDeleteVertexArrays((GLsizei)Label_Box_Data->nBuffers, Label_Box_Data->vaos); glDeleteBuffers((GLsizei)Label_Box_Data->nBuffers, Label_Box_Data->vbos); @@ -6602,6 +7174,12 @@ LoadFile(const char *filePath, memory_arena *arena, char **fileName, u64 *header glDeleteVertexArrays((GLsizei)Contig_ColourBar_Data->nBuffers, Contig_ColourBar_Data->vaos); glDeleteBuffers((GLsizei)Contig_ColourBar_Data->nBuffers, Contig_ColourBar_Data->vbos); + if (Contig_ColourBar_Data_GL_heap) + { + free(Contig_ColourBar_Data->vaos); + free(Contig_ColourBar_Data->vbos); + Contig_ColourBar_Data_GL_heap = 0; + } glDeleteVertexArrays((GLsizei)Scaff_Bar_Data->nBuffers, Scaff_Bar_Data->vaos); glDeleteBuffers((GLsizei)Scaff_Bar_Data->nBuffers, Scaff_Bar_Data->vbos); @@ -6896,6 +7474,7 @@ LoadFile(const char *filePath, memory_arena *arena, char **fileName, u64 *header // a pixel on screen. Contigs = PushStructP(arena, map_contigs); Contigs->numberOfContigs = nContigs; + Contigs->contigs_arr_capacity = nContigs; Contigs->contigs_arr = PushArrayP(arena, contig, nContigs); // Reserve storage for the contig invterted bit flags @@ -8342,13 +8921,13 @@ BreakMap( } fmt::print( - "[Pixel Cut] Original contig_id ({}), current_id ({}), pixel range: [{}, {}] {} inversed, cut at {}\n", + "[Pixel Cut] original_contig_id={} map_contig_id={} fragment_pixel_range=[{}, {}] {} cut_at_pixel={}\n", original_contig_id, - contig_id, - ptr_left, - ptr_right, - inversed ? "(is)" : "(isn\'t)", - loc ); + contig_id, + ptr_left, + ptr_right, + inversed ? "inverted" : "not inverted", + loc); return 1; @@ -12785,6 +13364,8 @@ void SortMapByMetaTags(u64 tagMask) MainArgs { + Terminal_Log_Init(); + Stuck_Problem_Check_Debug #ifdef PYTHON_SCOPED_INTERPRETER @@ -12918,6 +13499,7 @@ MainArgs { glfwSetWindowTitle(window, (const char *)currFileName); FenceIn(SetSaveStateNameBuffer((char *)currFileName)); + ApplyCrashReportPathFromMapPath((const char *)currFile); } } else @@ -12956,6 +13538,7 @@ MainArgs FileBrowserInit(&saveAGPBrowser, &media); FileBrowserInit(&saveEditsBrowser, &media); FileBrowserInit(&saveClipboardBrowser, &media); + FileBrowserInit(&saveDebugReportBrowser, &media); FileBrowserInit(&loadAGPBrowser, &media); } @@ -13066,6 +13649,7 @@ MainArgs { glfwSetWindowTitle(window, (const char *)currFileName); FenceIn(SetSaveStateNameBuffer((char *)currFileName)); + ApplyCrashReportPathFromMapPath((const char *)currFile); } glfwPollEvents(); Loading = 0; @@ -14494,6 +15078,28 @@ MainArgs AboutWindowRun(NK_Context, (u32)showAboutScreen); DebugWindowRun(NK_Context); + { + u08 dbg_save_state; + if ((dbg_save_state = FileBrowserRun("Save Debug Report", &saveDebugReportBrowser, NK_Context, (u32)Debug_Report_Save_Browser_Open, 5))) + { + WriteDebugReportToFile(saveDebugReportBrowser.file); + FileBrowserReloadDirectoryContent(&saveDebugReportBrowser, saveDebugReportBrowser.directory); + struct nk_window *w = nk_window_find(NK_Context, "Save Debug Report"); + if (w) + { + w->flags |= NK_WINDOW_HIDDEN; + } + Debug_Report_Save_Browser_Open = 0; + } + /* Do not clear the open flag just because the window is hidden (that blocked reopening after Save). + Clear when the dialog was closed (title bar) or collected — window missing or NK_WINDOW_CLOSED. */ + struct nk_window *save_dbg_w = nk_window_find(NK_Context, "Save Debug Report"); + if (Debug_Report_Save_Browser_Open && (!save_dbg_w || (save_dbg_w->flags & NK_WINDOW_CLOSED))) + { + Debug_Report_Save_Browser_Open = 0; + } + } + u08 state; if ((state = UserProfileEditorRun("User profile editor", &saveBrowser, NK_Context, (u32)showUserProfileScreen, 1))) { diff --git a/src/copy_texture.cpp b/src/copy_texture.cpp index d06ef0b..5375537 100644 --- a/src/copy_texture.cpp +++ b/src/copy_texture.cpp @@ -423,7 +423,7 @@ void TexturesArray4AI::copy_buffer_to_textures( /* -这个会把操作修改后的像素拷贝到 this->textures 中 +This will copy the modified pixels into this->textures. */ void TexturesArray4AI::copy_buffer_to_textures_dynamic( const contact_matrix *contact_matrix_, @@ -903,19 +903,51 @@ void TexturesArray4AI::cal_compressed_hic( select_area, false, // used for cut cluster_flag); - if (frags->total_length != (using_select_area ? select_area->get_selected_len(Contigs, cluster_flag) : num_pixels_1d)) - { + /* + num_pixels_1d is the size of the Hi‑C texture (num_textures_1d × texture_resolution), often a fixed grid (e.g. 32768). + frags->total_length is the sum of contig lengths in the map. + So you can have a few extra pixels in the texture that are not assigned to any contig (padding / unused tail). + The old check required total_length == num_pixels_1d for a full‑map sort, which is too strict and triggered + the assert even when the data is consistent.*/ + const u32 expected_len = + using_select_area ? select_area->get_selected_len(Contigs, cluster_flag) : num_pixels_1d; + + if (frags->total_length > num_pixels_1d) + { fmt::print( - "\n[Compress Hic] warning: frags->total_length({}) != num_pixels_1d ({}) ({}) ({}). file:{}, line:{}\n\n", + stderr, + "\n[Compress Hic] error: frags->total_length({}) > num_pixels_1d({}) — contigs extend past the Hi-C map. file:{}, line:{}\n\n", frags->total_length, - using_select_area ? select_area->get_selected_len(Contigs, cluster_flag) : num_pixels_1d, - (using_select_area?"selected area":"full area"), - cluster_flag?"clustering":"not clustering", + num_pixels_1d, __FILE__, - __LINE__ - ); + __LINE__); assert(0); } + + if (frags->total_length != expected_len) + { + if (!using_select_area && frags->total_length < num_pixels_1d) + { + fmt::print( + "\n[Compress Hic] note: frags->total_length({}) < num_pixels_1d({}) (full area) — map has unused trailing padding; continuing. file:{}, line:{}\n\n", + frags->total_length, + num_pixels_1d, + __FILE__, + __LINE__); + } + else + { + fmt::print( + "\n[Compress Hic] warning: frags->total_length({}) != expected_len({}) ({}) ({}). file:{}, line:{}\n\n", + frags->total_length, + expected_len, + (using_select_area ? "selected area" : "full area"), + cluster_flag ? "clustering" : "not clustering", + __FILE__, + __LINE__); + assert(0); + } + } // clean the memory of compressed_hic_mx if (compressed_hic) { diff --git a/src/genomeData.h b/src/genomeData.h index 5531465..f521839 100644 --- a/src/genomeData.h +++ b/src/genomeData.h @@ -78,7 +78,7 @@ struct map_contigs u08 *contigInvertFlags = nullptr; // invert flag [num_pixels_1d / 8], 1 bit for one pixel contig *contigs_arr = nullptr; u32 numberOfContigs; - u32 pad; + u32 contigs_arr_capacity; // bytes allocated for contigs_arr (see UpdateContigsFromMapState loop guard) };