fix: refine editor console logging

This commit is contained in:
2026-04-05 02:26:21 +08:00
parent cb7d60ec13
commit 811958351e
4 changed files with 123 additions and 68 deletions

View File

@@ -66,6 +66,12 @@ constexpr float kConsoleDetailsHeaderHeight = 24.0f;
constexpr float kConsoleToolbarItemSpacing = 4.0f; constexpr float kConsoleToolbarItemSpacing = 4.0f;
constexpr ImVec4 kConsoleToolbarBackgroundColor = ImVec4(0.23f, 0.23f, 0.23f, 1.0f); constexpr ImVec4 kConsoleToolbarBackgroundColor = ImVec4(0.23f, 0.23f, 0.23f, 1.0f);
float ResolveConsoleDetailsHeaderHeight() {
const ImGuiStyle& style = ImGui::GetStyle();
const float contentHeight = ImGui::GetTextLineHeight() + style.CellPadding.y * 2.0f;
return (std::max)(kConsoleDetailsHeaderHeight, contentHeight + 2.0f);
}
std::filesystem::path ResolveConsoleIconPath(const wchar_t* fileName) { std::filesystem::path ResolveConsoleIconPath(const wchar_t* fileName) {
const std::filesystem::path exeDir(XCEngine::Editor::Platform::Utf8ToWide(XCEngine::Editor::Platform::GetExecutableDirectoryUtf8())); const std::filesystem::path exeDir(XCEngine::Editor::Platform::Utf8ToWide(XCEngine::Editor::Platform::GetExecutableDirectoryUtf8()));
return (exeDir / L".." / L".." / L"resources" / L"Icons" / fileName).lexically_normal(); return (exeDir / L".." / L".." / L"resources" / L"Icons" / fileName).lexically_normal();
@@ -161,6 +167,29 @@ std::string BuildEntryKey(const LogEntry& entry) {
return key; return key;
} }
bool HasSourceLocation(const LogEntry& entry) {
return entry.file.Length() > 0 || entry.line > 0 || entry.function.Length() > 0;
}
std::string BuildCollapseKey(const LogEntry& entry) {
if (entry.category != XCEngine::Debug::LogCategory::Scripting || !HasSourceLocation(entry)) {
return BuildEntryKey(entry);
}
std::string key;
key.reserve(96 + entry.file.Length() + entry.function.Length());
key += std::to_string(static_cast<int>(entry.level));
key.push_back('\x1f');
key += std::to_string(static_cast<int>(entry.category));
key.push_back('\x1f');
key += entry.file.CStr();
key.push_back('\x1f');
key += std::to_string(entry.line);
key.push_back('\x1f');
key += entry.function.CStr();
return key;
}
std::string BuildSearchHaystack(const LogEntry& entry) { std::string BuildSearchHaystack(const LogEntry& entry) {
std::string haystack = XCEngine::Editor::UI::BuildConsoleLogText(entry); std::string haystack = XCEngine::Editor::UI::BuildConsoleLogText(entry);
haystack.push_back('\n'); haystack.push_back('\n');
@@ -205,32 +234,45 @@ std::vector<ConsoleRowData> BuildVisibleRows(
std::vector<ConsoleRowData> rows; std::vector<ConsoleRowData> rows;
rows.reserve(records.size()); rows.reserve(records.size());
if (!filterState.Collapse()) { std::unordered_map<std::string, size_t> occurrenceCountsByKey;
for (const EditorConsoleRecord& record : records) { occurrenceCountsByKey.reserve(records.size());
CountSeverity(counts, record.entry.level);
if (!filterState.Allows(record.entry.level) || !MatchesSearch(record.entry, searchQuery)) {
continue;
}
rows.push_back(ConsoleRowData{
record.serial,
record.entry,
BuildEntryKey(record.entry),
1u
});
}
return rows;
}
std::unordered_map<std::string, size_t> rowIndicesByKey;
rowIndicesByKey.reserve(records.size());
for (const EditorConsoleRecord& record : records) { for (const EditorConsoleRecord& record : records) {
CountSeverity(counts, record.entry.level); CountSeverity(counts, record.entry.level);
if (!filterState.Allows(record.entry.level) || !MatchesSearch(record.entry, searchQuery)) { if (!filterState.Allows(record.entry.level) || !MatchesSearch(record.entry, searchQuery)) {
continue; continue;
} }
const std::string entryKey = BuildEntryKey(record.entry); ++occurrenceCountsByKey[BuildCollapseKey(record.entry)];
}
if (!filterState.Collapse()) {
for (const EditorConsoleRecord& record : records) {
if (!filterState.Allows(record.entry.level) || !MatchesSearch(record.entry, searchQuery)) {
continue;
}
const std::string collapseKey = BuildCollapseKey(record.entry);
const auto countIt = occurrenceCountsByKey.find(collapseKey);
const size_t occurrenceCount = countIt != occurrenceCountsByKey.end() ? countIt->second : 1u;
rows.push_back(ConsoleRowData{
record.serial,
record.entry,
BuildEntryKey(record.entry),
occurrenceCount
});
}
return rows;
}
std::unordered_map<std::string, size_t> rowIndicesByKey;
rowIndicesByKey.reserve(occurrenceCountsByKey.size());
for (const EditorConsoleRecord& record : records) {
if (!filterState.Allows(record.entry.level) || !MatchesSearch(record.entry, searchQuery)) {
continue;
}
const std::string entryKey = BuildCollapseKey(record.entry);
const auto it = rowIndicesByKey.find(entryKey); const auto it = rowIndicesByKey.find(entryKey);
if (it == rowIndicesByKey.end()) { if (it == rowIndicesByKey.end()) {
rowIndicesByKey.emplace(entryKey, rows.size()); rowIndicesByKey.emplace(entryKey, rows.size());
@@ -699,32 +741,6 @@ ConsoleRowInteraction DrawConsoleRow(const ConsoleRowData& row, bool selected) {
summary.c_str()); summary.c_str());
drawList->PopClipRect(); drawList->PopClipRect();
if (hovered) {
const std::string sourceText = XCEngine::Editor::UI::BuildConsoleSourceText(row.entry);
XCEngine::Editor::UI::BeginTitledTooltip(XCEngine::Editor::UI::ConsoleSeverityLabel(row.entry.level));
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 28.0f);
ImGui::TextUnformatted(summary.c_str());
ImGui::PopTextWrapPos();
if (!sourceText.empty() || row.count > 1 || CanOpenSourceLocation(row.entry)) {
ImGui::Separator();
if (!sourceText.empty()) {
ImGui::TextColored(XCEngine::Editor::UI::ConsoleSecondaryTextColor(), "%s", sourceText.c_str());
}
if (row.count > 1) {
ImGui::TextColored(
XCEngine::Editor::UI::ConsoleSecondaryTextColor(),
"Occurrences: %zu",
row.count);
}
if (CanOpenSourceLocation(row.entry)) {
ImGui::TextColored(
XCEngine::Editor::UI::ConsoleSecondaryTextColor(),
"Double-click to open source");
}
}
XCEngine::Editor::UI::EndTitledTooltip();
}
return interaction; return interaction;
} }
@@ -1194,27 +1210,38 @@ void ConsolePanel::Render() {
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
const UI::SplitterResult splitter = UI::DrawSplitter("##ConsoleDetailsSplitter", UI::SplitterAxis::Horizontal, splitterThickness); const float splitterWidth = (std::max)(1.0f, ImGui::GetContentRegionAvail().x);
const UI::SplitterResult splitter = UI::DrawSplitter(
"##ConsoleDetailsSplitter",
UI::SplitterAxis::Horizontal,
splitterWidth,
splitterThickness);
if (splitter.active) { if (splitter.active) {
m_detailsHeight = std::clamp(m_detailsHeight - splitter.delta, minDetailsHeight, maxDetailsHeight); m_detailsHeight = std::clamp(m_detailsHeight - splitter.delta, minDetailsHeight, maxDetailsHeight);
} }
ImGui::SetCursorPosY((std::max)(0.0f, ImGui::GetCursorPosY() - ImGui::GetStyle().ItemSpacing.y));
selectedRow = ResolveSelectedRow(rows, m_selectedSerial, m_selectedEntryKey); selectedRow = ResolveSelectedRow(rows, m_selectedSerial, m_selectedEntryKey);
if (selectedRow) { if (selectedRow) {
m_selectedEntryKey = selectedRow->entryKey; m_selectedEntryKey = selectedRow->entryKey;
} }
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsBackgroundColor()); ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsBackgroundColor());
const bool detailsOpen = ImGui::BeginChild("ConsoleDetails", ImVec2(0.0f, 0.0f), false); const bool detailsOpen = ImGui::BeginChild("ConsoleDetails", ImVec2(0.0f, 0.0f), false);
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (detailsOpen) { if (detailsOpen) {
const float detailsHeaderHeight = ResolveConsoleDetailsHeaderHeight();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsHeaderBackgroundColor()); ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsHeaderBackgroundColor());
const bool headerOpen = ImGui::BeginChild( const bool headerOpen = ImGui::BeginChild(
"ConsoleDetailsHeader", "ConsoleDetailsHeader",
ImVec2(0.0f, kConsoleDetailsHeaderHeight), ImVec2(0.0f, detailsHeaderHeight),
false, false,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (headerOpen) { if (headerOpen) {
if (selectedRow) { if (selectedRow) {
const std::string sourceText = UI::BuildConsoleSourceText(selectedRow->entry); const std::string sourceText = UI::BuildConsoleSourceText(selectedRow->entry);
@@ -1257,10 +1284,12 @@ void ConsolePanel::Render() {
ImGui::EndTable(); ImGui::EndTable();
} }
} else { } else {
ImGui::AlignTextToFramePadding();
ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "Stack Trace"); ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "Stack Trace");
} }
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::SetCursorPosY((std::max)(0.0f, ImGui::GetCursorPosY() - ImGui::GetStyle().ItemSpacing.y));
if (selectedRow) { if (selectedRow) {
const std::string message = selectedRow->entry.message.CStr(); const std::string message = selectedRow->entry.message.CStr();

View File

@@ -458,22 +458,34 @@ MonoArray* CreateManagedComponentArray(MonoClass* componentClass, const std::vec
return array; return array;
} }
void InternalCall_Debug_Log(MonoString* message) { void LogManagedMessage(
XCEngine::Debug::Logger::Get().Info( XCEngine::Debug::LogLevel level,
MonoString* message,
MonoString* file,
int32_t line,
MonoString* member) {
const std::string messageText = MonoStringToUtf8(message);
const std::string fileText = MonoStringToUtf8(file);
const std::string memberText = MonoStringToUtf8(member);
XCEngine::Debug::Logger::Get().Log(
level,
XCEngine::Debug::LogCategory::Scripting, XCEngine::Debug::LogCategory::Scripting,
XCEngine::Containers::String(MonoStringToUtf8(message).c_str())); XCEngine::Containers::String(messageText.c_str()),
fileText.c_str(),
line,
memberText.c_str());
} }
void InternalCall_Debug_LogWarning(MonoString* message) { void InternalCall_Debug_Log(MonoString* message, MonoString* file, int32_t line, MonoString* member) {
XCEngine::Debug::Logger::Get().Warning( LogManagedMessage(XCEngine::Debug::LogLevel::Info, message, file, line, member);
XCEngine::Debug::LogCategory::Scripting,
XCEngine::Containers::String(MonoStringToUtf8(message).c_str()));
} }
void InternalCall_Debug_LogError(MonoString* message) { void InternalCall_Debug_LogWarning(MonoString* message, MonoString* file, int32_t line, MonoString* member) {
XCEngine::Debug::Logger::Get().Error( LogManagedMessage(XCEngine::Debug::LogLevel::Warning, message, file, line, member);
XCEngine::Debug::LogCategory::Scripting, }
XCEngine::Containers::String(MonoStringToUtf8(message).c_str()));
void InternalCall_Debug_LogError(MonoString* message, MonoString* file, int32_t line, MonoString* member) {
LogManagedMessage(XCEngine::Debug::LogLevel::Error, message, file, line, member);
} }
float InternalCall_Time_GetDeltaTime() { float InternalCall_Time_GetDeltaTime() {

View File

@@ -1,20 +1,34 @@
using System.Runtime.CompilerServices;
namespace XCEngine namespace XCEngine
{ {
public static class Debug public static class Debug
{ {
public static void Log(string message) public static void Log(
string message,
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string member = "")
{ {
InternalCalls.Debug_Log(message ?? string.Empty); InternalCalls.Debug_Log(message ?? string.Empty, file ?? string.Empty, line, member ?? string.Empty);
} }
public static void LogWarning(string message) public static void LogWarning(
string message,
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string member = "")
{ {
InternalCalls.Debug_LogWarning(message ?? string.Empty); InternalCalls.Debug_LogWarning(message ?? string.Empty, file ?? string.Empty, line, member ?? string.Empty);
} }
public static void LogError(string message) public static void LogError(
string message,
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string member = "")
{ {
InternalCalls.Debug_LogError(message ?? string.Empty); InternalCalls.Debug_LogError(message ?? string.Empty, file ?? string.Empty, line, member ?? string.Empty);
} }
} }
} }

View File

@@ -6,13 +6,13 @@ namespace XCEngine
internal static class InternalCalls internal static class InternalCalls
{ {
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Debug_Log(string message); internal static extern void Debug_Log(string message, string file, int line, string member);
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Debug_LogWarning(string message); internal static extern void Debug_LogWarning(string message, string file, int line, string member);
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Debug_LogError(string message); internal static extern void Debug_LogError(string message, string file, int line, string member);
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
internal static extern float Time_GetDeltaTime(); internal static extern float Time_GetDeltaTime();