SDSORT_QUICK (#28069)

This commit is contained in:
Andrew
2025-10-16 18:30:22 -04:00
committed by GitHub
parent 70d910ac8e
commit 21f0333b43
3 changed files with 179 additions and 79 deletions
+1
View File
@@ -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
+173 -75
View File
@@ -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)
+5 -4
View File
@@ -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