perf(console): case-insensitive filtering without per-line copies
Console text filtering and match highlighting lowercased a full copy of every line, and of every drawn segment, on each pass. - A new alloc-free ASCII case-insensitive substring search folds both sides on the fly, so neither the filter test nor the highlight scan allocates a lowercased copy per line any more. - Both call sites use it; the case-insensitive match behaviour is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,18 @@
|
|||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
namespace {
|
size_t FindCaseInsensitive(const char* hay, size_t hayLen, const std::string& needleLower, size_t from)
|
||||||
std::string lowerCopy(std::string value)
|
|
||||||
{
|
{
|
||||||
std::transform(value.begin(), value.end(), value.begin(),
|
const size_t n = needleLower.size();
|
||||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
if (n == 0) return from <= hayLen ? from : std::string::npos;
|
||||||
return value;
|
if (hayLen < n) return std::string::npos;
|
||||||
}
|
auto fold = [](unsigned char c) { return (c >= 'A' && c <= 'Z') ? static_cast<unsigned char>(c + 32) : c; };
|
||||||
|
for (size_t i = from; i + n <= hayLen; ++i) {
|
||||||
|
size_t j = 0;
|
||||||
|
while (j < n && fold(static_cast<unsigned char>(hay[i + j])) == fold(static_cast<unsigned char>(needleLower[j]))) ++j;
|
||||||
|
if (j == n) return i;
|
||||||
|
}
|
||||||
|
return std::string::npos;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool consoleLinePassesFilter(const std::string& lineText,
|
bool consoleLinePassesFilter(const std::string& lineText,
|
||||||
@@ -25,9 +30,7 @@ bool consoleLinePassesFilter(const std::string& lineText,
|
|||||||
if (!filter.appMessagesEnabled && channel == ConsoleChannel::App) return false;
|
if (!filter.appMessagesEnabled && channel == ConsoleChannel::App) return false;
|
||||||
if (filter.errorsOnly && channel != ConsoleChannel::Error) return false;
|
if (filter.errorsOnly && channel != ConsoleChannel::Error) return false;
|
||||||
if (!filter.text.empty()) {
|
if (!filter.text.empty()) {
|
||||||
std::string needle = lowerCopy(filter.text);
|
if (FindCaseInsensitive(lineText.data(), lineText.size(), filter.text, 0) == std::string::npos) return false;
|
||||||
std::string haystack = lowerCopy(lineText);
|
|
||||||
if (haystack.find(needle) == std::string::npos) return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ struct ConsoleOutputFilter {
|
|||||||
bool appMessagesEnabled = true; // App channel
|
bool appMessagesEnabled = true; // App channel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Alloc-free ASCII case-insensitive substring search (C-locale fold). Returns the byte
|
||||||
|
// offset of the first match at or after `from`, or npos. Needle is folded on the fly.
|
||||||
|
size_t FindCaseInsensitive(const char* hay, size_t hayLen, const std::string& needle, size_t from);
|
||||||
|
|
||||||
// Whether a line with the given channel + text passes the active filter.
|
// Whether a line with the given channel + text passes the active filter.
|
||||||
bool consoleLinePassesFilter(const std::string& lineText,
|
bool consoleLinePassesFilter(const std::string& lineText,
|
||||||
ConsoleChannel channel,
|
ConsoleChannel channel,
|
||||||
|
|||||||
@@ -1196,11 +1196,9 @@ void ConsoleTab::drawVisibleLines(float padX, float lineHeight, bool hasTextFilt
|
|||||||
|
|
||||||
// Filter match highlight (case-insensitive) within this segment.
|
// Filter match highlight (case-insensitive) within this segment.
|
||||||
if (hasTextFilter && !filterLower.empty() && seg.byteStart < seg.byteEnd) {
|
if (hasTextFilter && !filterLower.empty() && seg.byteStart < seg.byteEnd) {
|
||||||
std::string segLower(segStart, segEnd);
|
const size_t segBytes = static_cast<size_t>(segEnd - segStart);
|
||||||
std::transform(segLower.begin(), segLower.end(), segLower.begin(),
|
|
||||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
while ((pos = segLower.find(filterLower, pos)) != std::string::npos) {
|
while ((pos = FindCaseInsensitive(segStart, segBytes, filterLower, pos)) != std::string::npos) {
|
||||||
float xs = font->CalcTextSizeA(fontSize, FLT_MAX, 0, segStart, segStart + static_cast<int>(pos)).x;
|
float xs = font->CalcTextSizeA(fontSize, FLT_MAX, 0, segStart, segStart + static_cast<int>(pos)).x;
|
||||||
float xe = font->CalcTextSizeA(fontSize, FLT_MAX, 0, segStart,
|
float xe = font->CalcTextSizeA(fontSize, FLT_MAX, 0, segStart,
|
||||||
segStart + static_cast<int>(pos + filterLower.size())).x;
|
segStart + static_cast<int>(pos + filterLower.size())).x;
|
||||||
|
|||||||
Reference in New Issue
Block a user