diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index f64ee9def4..2da759ab3c 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1877,6 +1877,7 @@ #define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use! #define SDSORT_CACHE_VFATS 2 // Maximum number of 13-byte VFAT entries to use for sorting. // Note: Only affects SCROLL_LONG_FILENAMES with SDSORT_CACHE_NAMES but not SDSORT_DYNAMIC_RAM. + #define SDSORT_QUICK true // Use Quick Sort as a sorting algorithm. Otherwise use Bubble Sort. #endif // Allow international symbols in long filenames. To display correctly, the diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index 45e22d3f1e..9a5394d5e7 100644 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -303,7 +303,7 @@ void CardReader::printListing(MediaFile parent, const char * const prepend, cons // Allocate enough stack space for the full path including / separator char path[lenPrepend + FILENAME_LENGTH]; if (prepend) { strcpy(path, prepend); path[lenPrepend - 1] = '/'; } - char* dosFilename = path + lenPrepend; + char * const dosFilename = path + lenPrepend; createFilename(dosFilename, p); // Get a new directory object using the full path @@ -1328,6 +1328,171 @@ void CardReader::cdroot() { #endif #endif + #if ENABLED(SDSORT_QUICK) + + // Quick Sort + bool CardReader::sort_cmp_files(const int16_t o1, const int16_t o2) { + auto _sort_cmp_file = [](const char *const n1, const char *const n2) -> bool { + const bool sort = strcasecmp(n1, n2) < 0; + return (TERN(SDSORT_GCODE, sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE))) ? !sort : sort; + }; + + #if ENABLED(SDSORT_USES_RAM) + const bool dir1 = IS_DIR(o1), dir2 = IS_DIR(o2); + const char *name1 = card.sortnames[o1], *name2 = card.sortnames[o2]; + #else + card.selectFileByIndex(o1); + char name1_buffer[LONG_FILENAME_LENGTH]; + strcpy(name1_buffer, card.longest_filename()); + const char *name1 = name1_buffer; + const bool dir1 = card.flag.filenameIsDir; + + card.selectFileByIndex(o2); + const char *name2 = card.longest_filename(); + const bool dir2 = card.flag.filenameIsDir; + #endif + + #if HAS_FOLDER_SORTING + #if ENABLED(SDSORT_GCODE) + if (card.sort_folders && dir1 != dir2) + return (card.sort_folders > 0) ? dir1 : !dir1; + #else + if (dir1 != dir2) + return (SDSORT_FOLDERS > 0) ? dir1 : !dir1; + #endif + #endif + + return _sort_cmp_file(name1, name2); + } + + int16_t CardReader::partition(uint8_t* arr, int16_t low, int16_t high) { + int16_t pivotIndex = arr[high]; + int16_t i = (low - 1); + + for (int16_t j = low; j < high; j++) { + if (sort_cmp_files(arr[j], pivotIndex)) { + i++; + uint8_t temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + // Manual swap + uint8_t temp = arr[i + 1]; + arr[i + 1] = arr[high]; + arr[high] = temp; + return (i + 1); + } + + void CardReader::quicksort(uint8_t* arr, int16_t low, int16_t high) { + int16_t stack[SDSORT_LIMIT + 1]; + int16_t top = -1; // Initialize top of stack + + // Push initial values to the stack + stack[++top] = low; + stack[++top] = high; + + // Pop from stack while not empty + while (top >= 0) { + high = stack[top--]; + low = stack[top--]; + + // Set pivot element at correct position + const int16_t pivot = partition(arr, low, high); + // If elements are on left side, push to stack + if (pivot - 1 > low) { + stack[++top] = low; + stack[++top] = pivot - 1; + } + // If elements are on right side, push to stack + if (pivot + 1 < high) { + stack[++top] = pivot + 1; + stack[++top] = high; + } + } + } + + #else // !SDSORT_QUICK + + // Bubble Sort + void CardReader::bubblesort(uint8_t* arr, int16_t fileCnt) { + for (int16_t i = fileCnt; --i;) { + bool didSwap = false; + int16_t o1 = arr[0]; + #if DISABLED(SDSORT_USES_RAM) + // By default re-read the names from SD for every compare + // retaining only two filenames at a time. This is very + // slow but is safest and uses minimal RAM. + char name1[LONG_FILENAME_LENGTH]; + selectFileByIndex(o1); // Pre-fetch the first entry and save it + strcpy(name1, longest_filename()); // so the loop only needs one fetch + #if HAS_FOLDER_SORTING + bool dir1 = flag.filenameIsDir; + #endif + if ((i & 0x7) == 7) hal.watchdog_refresh(); + #endif + + for (int16_t j = 0; j < i; ++j) { + const int16_t o2 = arr[j + 1]; + + // Compare names from the array or just the two buffered names + auto _sort_cmp_file = [](char * const n1, char * const n2) -> bool { + const bool sort = strcasecmp(n1, n2) > 0; + return (TERN(SDSORT_GCODE, sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE))) ? !sort : sort; + }; + #define _SORT_CMP_FILE() _sort_cmp_file(TERN(SDSORT_USES_RAM, sortnames[o1], name1), TERN(SDSORT_USES_RAM, sortnames[o2], name2)) + + #if HAS_FOLDER_SORTING + #if ENABLED(SDSORT_USES_RAM) + // Folder sorting needs an index and bit to test for folder-ness. + #define _SORT_CMP_DIR(fs) (IS_DIR(o1) == IS_DIR(o2) ? _SORT_CMP_FILE() : IS_DIR(fs > 0 ? o1 : o2)) + #else + #define _SORT_CMP_DIR(fs) ((dir1 == flag.filenameIsDir) ? _SORT_CMP_FILE() : (fs > 0 ? dir1 : !dir1)) + #endif + #endif + + // The most economical method reads names as-needed + // throughout the loop. Slow if there are many. + #if DISABLED(SDSORT_USES_RAM) + selectFileByIndex(o2); + const bool dir2 = flag.filenameIsDir; + char * const name2 = longest_filename(); // Use the string in-place + if ((i & 0x7) == 7) hal.watchdog_refresh(); + #endif + + // Sort the current pair according to settings. + if ( + #if HAS_FOLDER_SORTING + #if ENABLED(SDSORT_GCODE) + sort_folders ? _SORT_CMP_DIR(sort_folders) : _SORT_CMP_FILE() + #else + _SORT_CMP_DIR(SDSORT_FOLDERS) + #endif + #else + _SORT_CMP_FILE() + #endif + ) { + // Reorder the index, indicate that sorting happened + // Note that the next o1 will be the current o1. No new fetch needed. + arr[j] = o2; + arr[j + 1] = o1; + didSwap = true; + } + else { + // The next o1 is the current o2. No new fetch needed. + o1 = o2; + #if DISABLED(SDSORT_USES_RAM) + TERN_(HAS_FOLDER_SORTING, dir1 = dir2); + strcpy(name1, name2); + #endif + } + } + if (!didSwap) break; + } + } + + #endif // !SDSORT_QUICK + /** * Read all the files and produce a sort key * @@ -1378,13 +1543,6 @@ void CardReader::cdroot() { #endif #endif - #else // !SDSORT_USES_RAM - - // By default re-read the names from SD for every compare - // retaining only two filenames at a time. This is very - // slow but is safest and uses minimal RAM. - char name1[LONG_FILENAME_LENGTH]; - #endif if (fileCnt > 1) { @@ -1406,76 +1564,16 @@ void CardReader::cdroot() { if (flag.filenameIsDir) SBI(isDir[ind], bit); #endif #endif + if ((i & 0x7) == 7) hal.watchdog_refresh(); } - // Bubble Sort - for (int16_t i = fileCnt; --i;) { - bool didSwap = false; - int16_t o1 = sort_order[0]; - #if DISABLED(SDSORT_USES_RAM) - selectFileByIndex(o1); // Pre-fetch the first entry and save it - strcpy(name1, longest_filename()); // so the loop only needs one fetch - #if HAS_FOLDER_SORTING - bool dir1 = flag.filenameIsDir; - #endif - #endif + // Sorting Algorithm + #if ENABLED(SDSORT_QUICK) + quicksort(sort_order, 0, fileCnt - 1); + #else + bubblesort(sort_order, fileCnt); + #endif - for (int16_t j = 0; j < i; ++j) { - const int16_t o2 = sort_order[j + 1]; - - // Compare names from the array or just the two buffered names - auto _sort_cmp_file = [](char * const n1, char * const n2) -> bool { - const bool sort = strcasecmp(n1, n2) > 0; - return (TERN(SDSORT_GCODE, card.sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE))) ? !sort : sort; - }; - #define _SORT_CMP_FILE() _sort_cmp_file(TERN(SDSORT_USES_RAM, sortnames[o1], name1), TERN(SDSORT_USES_RAM, sortnames[o2], name2)) - - #if HAS_FOLDER_SORTING - #if ENABLED(SDSORT_USES_RAM) - // Folder sorting needs an index and bit to test for folder-ness. - #define _SORT_CMP_DIR(fs) (IS_DIR(o1) == IS_DIR(o2) ? _SORT_CMP_FILE() : IS_DIR(fs > 0 ? o1 : o2)) - #else - #define _SORT_CMP_DIR(fs) ((dir1 == flag.filenameIsDir) ? _SORT_CMP_FILE() : (fs > 0 ? dir1 : !dir1)) - #endif - #endif - - // The most economical method reads names as-needed - // throughout the loop. Slow if there are many. - #if DISABLED(SDSORT_USES_RAM) - selectFileByIndex(o2); - const bool dir2 = flag.filenameIsDir; - char * const name2 = longest_filename(); // use the string in-place - #endif // !SDSORT_USES_RAM - - // Sort the current pair according to settings. - if ( - #if HAS_FOLDER_SORTING - #if ENABLED(SDSORT_GCODE) - sort_folders ? _SORT_CMP_DIR(sort_folders) : _SORT_CMP_FILE() - #else - _SORT_CMP_DIR(SDSORT_FOLDERS) - #endif - #else - _SORT_CMP_FILE() - #endif - ) { - // Reorder the index, indicate that sorting happened - // Note that the next o1 will be the current o1. No new fetch needed. - sort_order[j] = o2; - sort_order[j + 1] = o1; - didSwap = true; - } - else { - // The next o1 is the current o2. No new fetch needed. - o1 = o2; - #if DISABLED(SDSORT_USES_RAM) - TERN_(HAS_FOLDER_SORTING, dir1 = dir2); - strcpy(name1, name2); - #endif - } - } - if (!didSwap) break; - } // Using RAM but not keeping names around #if ENABLED(SDSORT_USES_RAM) && DISABLED(SDSORT_CACHE_NAMES) #if ENABLED(SDSORT_DYNAMIC_RAM) diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h index 19e08b5b4d..5f1d99a561 100644 --- a/Marlin/src/sd/cardreader.h +++ b/Marlin/src/sd/cardreader.h @@ -402,6 +402,11 @@ private: #endif // SDSORT_USES_RAM + static void flush_presort(); + static bool sort_cmp_files(const int16_t o1, const int16_t o2); + static int16_t partition(uint8_t* arr, int16_t low, int16_t high); + static void quicksort(uint8_t* arr, int16_t low, int16_t high); + static void bubblesort(uint8_t* arr, int16_t fileCnt); #endif // SDCARD_SORT_ALPHA // @@ -424,10 +429,6 @@ private: MediaFile parent, const char * const prepend, const uint8_t lsflags OPTARG(LONG_FILENAME_HOST_SUPPORT, const char * const prependLong=nullptr) ); - - #if ENABLED(SDCARD_SORT_ALPHA) - static void flush_presort(); - #endif }; #else // !HAS_MEDIA