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 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) {
const std::filesystem::path exeDir(XCEngine::Editor::Platform::Utf8ToWide(XCEngine::Editor::Platform::GetExecutableDirectoryUtf8()));
return (exeDir / L".." / L".." / L"resources" / L"Icons" / fileName).lexically_normal();
@@ -161,6 +167,29 @@ std::string BuildEntryKey(const LogEntry& entry) {
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 haystack = XCEngine::Editor::UI::BuildConsoleLogText(entry);
haystack.push_back('\n');
@@ -205,32 +234,45 @@ std::vector<ConsoleRowData> BuildVisibleRows(
std::vector<ConsoleRowData> rows;
rows.reserve(records.size());
if (!filterState.Collapse()) {
for (const EditorConsoleRecord& record : records) {
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());
std::unordered_map<std::string, size_t> occurrenceCountsByKey;
occurrenceCountsByKey.reserve(records.size());
for (const EditorConsoleRecord& record : records) {
CountSeverity(counts, record.entry.level);
if (!filterState.Allows(record.entry.level) || !MatchesSearch(record.entry, searchQuery)) {
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);
if (it == rowIndicesByKey.end()) {
rowIndicesByKey.emplace(entryKey, rows.size());
@@ -699,32 +741,6 @@ ConsoleRowInteraction DrawConsoleRow(const ConsoleRowData& row, bool selected) {
summary.c_str());
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;
}
@@ -1194,27 +1210,38 @@ void ConsolePanel::Render() {
ImGui::EndChild();
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) {
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);
if (selectedRow) {
m_selectedEntryKey = selectedRow->entryKey;
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsBackgroundColor());
const bool detailsOpen = ImGui::BeginChild("ConsoleDetails", ImVec2(0.0f, 0.0f), false);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (detailsOpen) {
const float detailsHeaderHeight = ResolveConsoleDetailsHeaderHeight();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsHeaderBackgroundColor());
const bool headerOpen = ImGui::BeginChild(
"ConsoleDetailsHeader",
ImVec2(0.0f, kConsoleDetailsHeaderHeight),
ImVec2(0.0f, detailsHeaderHeight),
false,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (headerOpen) {
if (selectedRow) {
const std::string sourceText = UI::BuildConsoleSourceText(selectedRow->entry);
@@ -1257,10 +1284,12 @@ void ConsolePanel::Render() {
ImGui::EndTable();
}
} else {
ImGui::AlignTextToFramePadding();
ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "Stack Trace");
}
}
ImGui::EndChild();
ImGui::SetCursorPosY((std::max)(0.0f, ImGui::GetCursorPosY() - ImGui::GetStyle().ItemSpacing.y));
if (selectedRow) {
const std::string message = selectedRow->entry.message.CStr();

View File

@@ -458,22 +458,34 @@ MonoArray* CreateManagedComponentArray(MonoClass* componentClass, const std::vec
return array;
}
void InternalCall_Debug_Log(MonoString* message) {
XCEngine::Debug::Logger::Get().Info(
void LogManagedMessage(
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::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) {
XCEngine::Debug::Logger::Get().Warning(
XCEngine::Debug::LogCategory::Scripting,
XCEngine::Containers::String(MonoStringToUtf8(message).c_str()));
void InternalCall_Debug_Log(MonoString* message, MonoString* file, int32_t line, MonoString* member) {
LogManagedMessage(XCEngine::Debug::LogLevel::Info, message, file, line, member);
}
void InternalCall_Debug_LogError(MonoString* message) {
XCEngine::Debug::Logger::Get().Error(
XCEngine::Debug::LogCategory::Scripting,
XCEngine::Containers::String(MonoStringToUtf8(message).c_str()));
void InternalCall_Debug_LogWarning(MonoString* message, MonoString* file, int32_t line, MonoString* member) {
LogManagedMessage(XCEngine::Debug::LogLevel::Warning, message, file, line, member);
}
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() {

View File

@@ -1,20 +1,34 @@
using System.Runtime.CompilerServices;
namespace XCEngine
{
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
{
[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)]
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)]
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)]
internal static extern float Time_GetDeltaTime();