Error Handling: enabled experimental recovery systems. (#1651, #5654)

Setup a couple of features to configure them, including ways to display error tooltips instead of assserting.
This commit is contained in:
ocornut 2024-09-24 20:51:51 +02:00
parent 8776678a46
commit 30c29d291f
7 changed files with 203 additions and 96 deletions

View File

@ -43,8 +43,22 @@ Breaking changes:
Other changes: Other changes:
- Error Handling: rewired asserts in PopID(), PopFont(), PopItemFlag(), EndDisabled(), - Error Handling: Enabled/improved error recovery systems. (#1651, #5654)
PopTextWrapPos(), PopFocusScope(), PopItemWidth() to use IM_ASSERT_USER_ERROR(). (#1651) - Error recovery is not perfect nor guaranteed! It is a feature to ease development.
- Functions that support error recovery are using IM_ASSERT_USER_ERROR() instead of IM_ASSERT().
- You not are not supposed to rely on it in the course of a normal application run.
- Possible usage: facilitate recovery from errors triggered from a scripting language or
after specific exceptions handlers. Surface errors to programmers in less agressive ways.
- Always ensure that on programmers seats you have at minimum Asserts or Tooltips enabled
when making direct imgui API calls! Otherwise it would severely hinder your ability to
catch and correct mistakes!
- Added io.ConfigErrorRecovery to enable error recovery support.
- Added io.ConfigErrorRecoveryEnableAssert to assert on recoverable errors.
- Added io.ConfigErrorRecoveryEnableDebugLog to output to debug log on recoverable errors.
- Added io.ConfigErrorRecoveryEnableTooltip to enable displaying an error tooltip on recoverable errors.
The tooltip include a way to enable asserts if they were disabled.
- All options are enabled by default.
- Read https://github.com/ocornut/imgui/wiki/Error-Handling for a bit more details.
- Windows: BeginChild(): made it possible to call SetNextWindowSize() on a child window - Windows: BeginChild(): made it possible to call SetNextWindowSize() on a child window
using ImGuiChildFlags_ResizeX,ImGuiChildFlags_ResizeY in order to override its current using ImGuiChildFlags_ResizeX,ImGuiChildFlags_ResizeY in order to override its current
size. (#1710, #8020) size. (#1710, #8020)

196
imgui.cpp
View File

@ -1401,6 +1401,11 @@ ImGuiIO::ImGuiIO()
ConfigDebugBeginReturnValueOnce = false; ConfigDebugBeginReturnValueOnce = false;
ConfigDebugBeginReturnValueLoop = false; ConfigDebugBeginReturnValueLoop = false;
ConfigErrorRecovery = true;
ConfigErrorRecoveryEnableAssert = true;
ConfigErrorRecoveryEnableDebugLog = true;
ConfigErrorRecoveryEnableTooltip = true;
// Inputs Behaviors // Inputs Behaviors
MouseDoubleClickTime = 0.30f; MouseDoubleClickTime = 0.30f;
MouseDoubleClickMaxDist = 6.0f; MouseDoubleClickMaxDist = 6.0f;
@ -3978,6 +3983,12 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
LogDepthRef = 0; LogDepthRef = 0;
LogDepthToExpand = LogDepthToExpandDefault = 2; LogDepthToExpand = LogDepthToExpandDefault = 2;
ErrorCallback = NULL;
ErrorCallbackUserData = NULL;
ErrorFirst = true;
ErrorCountCurrentFrame = 0;
StackSizesInBeginForCurrentWindow = NULL;
DebugDrawIdConflictsCount = 0; DebugDrawIdConflictsCount = 0;
DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY; DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY;
DebugLocateId = 0; DebugLocateId = 0;
@ -4211,6 +4222,7 @@ static void SetCurrentWindow(ImGuiWindow* window)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
g.CurrentWindow = window; g.CurrentWindow = window;
g.StackSizesInBeginForCurrentWindow = g.CurrentWindow ? &g.CurrentWindowStack.back().StackSizesInBegin : NULL;
g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL;
g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking
if (window) if (window)
@ -5249,6 +5261,10 @@ void ImGui::NewFrame()
Begin("Debug##Default"); Begin("Debug##Default");
IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true); IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);
// Store stack sizes
g.ErrorCountCurrentFrame = 0;
ErrorRecoveryStoreState(&g.StackSizesInNewFrame);
// [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack, // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack,
// allowing to validate correct Begin/End behavior in user code. // allowing to validate correct Begin/End behavior in user code.
#ifndef IMGUI_DISABLE_DEBUG_TOOLS #ifndef IMGUI_DISABLE_DEBUG_TOOLS
@ -5467,6 +5483,9 @@ void ImGui::EndFrame()
CallContextHooks(&g, ImGuiContextHookType_EndFramePre); CallContextHooks(&g, ImGuiContextHookType_EndFramePre);
// [EXPERIMENTAL] Recover from errors
if (g.IO.ConfigErrorRecovery)
ErrorRecoveryTryToRecoverState(&g.StackSizesInNewFrame);
ErrorCheckEndFrameSanityChecks(); ErrorCheckEndFrameSanityChecks();
ErrorCheckEndFrameFinalizeErrorTooltip(); ErrorCheckEndFrameFinalizeErrorTooltip();
@ -6960,12 +6979,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Add to stack // Add to stack
g.CurrentWindow = window; g.CurrentWindow = window;
ImGuiWindowStackData window_stack_data; g.CurrentWindowStack.resize(g.CurrentWindowStack.Size + 1);
ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back();
window_stack_data.Window = window; window_stack_data.Window = window;
window_stack_data.ParentLastItemDataBackup = g.LastItemData; window_stack_data.ParentLastItemDataBackup = g.LastItemData;
window_stack_data.StackSizesOnBegin.SetToContextState(&g);
window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled); window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled);
g.CurrentWindowStack.push_back(window_stack_data); ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin);
g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin;
if (flags & ImGuiWindowFlags_ChildMenu) if (flags & ImGuiWindowFlags_ChildMenu)
g.BeginMenuDepth++; g.BeginMenuDepth++;
@ -7693,7 +7713,11 @@ void ImGui::End()
g.BeginMenuDepth--; g.BeginMenuDepth--;
if (window->Flags & ImGuiWindowFlags_Popup) if (window->Flags & ImGuiWindowFlags_Popup)
g.BeginPopupStack.pop_back(); g.BeginPopupStack.pop_back();
window_stack_data.StackSizesOnBegin.CompareWithContextState(&g);
// Error handling, state recovery
if (g.IO.ConfigErrorRecovery)
ErrorRecoveryTryToRecoverWindowState(&window_stack_data.StackSizesInBegin);
g.CurrentWindowStack.pop_back(); g.CurrentWindowStack.pop_back();
SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);
} }
@ -8454,7 +8478,7 @@ void ImGui::PushFocusScope(ImGuiID id)
void ImGui::PopFocusScope() void ImGui::PopFocusScope()
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
if (g.FocusScopeStack.Size <= 0) if (g.FocusScopeStack.Size <= g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack)
{ {
IM_ASSERT_USER_ERROR(0, "Calling PopFocusScope() too many times!"); IM_ASSERT_USER_ERROR(0, "Calling PopFocusScope() too many times!");
return; return;
@ -10303,9 +10327,10 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own
// - ErrorCheckUsingSetCursorPosToExtendParentBoundaries() // - ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
// - ErrorCheckNewFrameSanityChecks() // - ErrorCheckNewFrameSanityChecks()
// - ErrorCheckEndFrameSanityChecks() // - ErrorCheckEndFrameSanityChecks()
// - ErrorCheckEndFrameRecover() // - ErrorRecoveryStoreState()
// - ErrorCheckEndWindowRecover() // - ErrorRecoveryTryToRecoverState()
// - ImGuiStackSizes // - ErrorRecoveryTryToRecoverWindowState()
// - ErrorLog()
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues.
@ -10404,6 +10429,10 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
#endif #endif
// Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way.
if (g.IO.ConfigErrorRecovery)
IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL);
// Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024) // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl)) if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl))
@ -10426,43 +10455,35 @@ static void ImGui::ErrorCheckEndFrameSanityChecks()
IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
IM_UNUSED(key_mods); IM_UNUSED(key_mods);
// [EXPERIMENTAL] Recover from errors: You may call this yourself before EndFrame(). IM_ASSERT(g.CurrentWindowStack.Size == 1);
//ErrorCheckEndFrameRecover(); IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow);
// Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you
// to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API).
if (g.CurrentWindowStack.Size != 1)
{
if (g.CurrentWindowStack.Size > 1)
{
ImGuiWindow* window = g.CurrentWindowStack.back().Window; // <-- This window was not Ended!
IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
IM_UNUSED(window);
while (g.CurrentWindowStack.Size > 1)
End();
}
else
{
IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
}
}
if (g.CurrentWindowStack.Size >= 1)
IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow);
IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!");
} }
// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls. // Save current stack sizes. Called e.g. by NewFrame() and by Begin() but may be called for manual recovery.
// Must be called during or before EndFrame(). void ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out)
// This is generally flawed as we are not necessarily End/Popping things in the right order. {
// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. ImGuiContext& g = *GImGui;
void ImGui::ErrorCheckEndFrameRecover() state_out->SizeOfWindowStack = (short)g.CurrentWindowStack.Size;
state_out->SizeOfIDStack = (short)g.CurrentWindow->IDStack.Size;
state_out->SizeOfColorStack = (short)g.ColorStack.Size;
state_out->SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
state_out->SizeOfFontStack = (short)g.FontStack.Size;
state_out->SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
state_out->SizeOfGroupStack = (short)g.GroupStack.Size;
state_out->SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;
state_out->SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
state_out->SizeOfDisabledStack = (short)g.DisabledStackSize;
}
// Chosen name "Try to recover" over e.g. "Restore" to suggest this is not a 100% guaranteed recovery.
// Called by e.g. EndFrame() but may be called for manual recovery.
// Attempt to recover full window stack.
void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in)
{ {
// PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations" // PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations"
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
while (g.CurrentWindowStack.Size > 0) //-V1044 while (g.CurrentWindowStack.Size > state_in->SizeOfWindowStack) //-V1044
{ {
ErrorCheckEndWindowRecover();
// Recap: // Recap:
// - Begin()/BeginChild() return false to indicate the window is collapsed or fully clipped. // - Begin()/BeginChild() return false to indicate the window is collapsed or fully clipped.
// - Always call a matching End() for each Begin() call, regardless of its return value! // - Always call a matching End() for each Begin() call, regardless of its return value!
@ -10480,12 +10501,17 @@ void ImGui::ErrorCheckEndFrameRecover()
End(); End();
} }
} }
if (g.CurrentWindowStack.Size == state_in->SizeOfWindowStack)
ErrorRecoveryTryToRecoverWindowState(state_in);
} }
// Must be called before End()/EndChild() // Called by e.g. End() but may be called for manual recovery.
void ImGui::ErrorCheckEndWindowRecover() // Read '// Error Handling [BETA]' block in imgui_internal.h for details.
// Attempt to recover from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.
void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow)
{ {
IM_ASSERT_USER_ERROR(0, "Missing EndTable()"); IM_ASSERT_USER_ERROR(0, "Missing EndTable()");
@ -10493,8 +10519,6 @@ void ImGui::ErrorCheckEndWindowRecover()
} }
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(window != NULL);
ImGuiStackSizes* state_in = &g.CurrentWindowStack.back().StackSizesOnBegin;
// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. // FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044 while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044
@ -10560,45 +10584,52 @@ void ImGui::ErrorCheckEndWindowRecover()
IM_ASSERT_USER_ERROR(0, "Missing PopFocusScope()"); IM_ASSERT_USER_ERROR(0, "Missing PopFocusScope()");
PopFocusScope(); PopFocusScope();
} }
//IM_ASSERT(g.FocusScopeStack.Size == state_in->SizeOfFocusScopeStack);
} }
// Save current stack sizes for later compare bool ImGui::ErrorLog(const char* msg)
void ImGuiStackSizes::SetToContextState(ImGuiContext* ctx)
{ {
ImGuiContext& g = *ctx; ImGuiContext& g = *GImGui;
// Output to debug log
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
SizeOfIDStack = (short)window->IDStack.Size;
SizeOfColorStack = (short)g.ColorStack.Size;
SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
SizeOfFontStack = (short)g.FontStack.Size;
SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
SizeOfGroupStack = (short)g.GroupStack.Size;
SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;
SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
SizeOfDisabledStack = (short)g.DisabledStackSize;
}
// Compare to detect usage errors if (g.IO.ConfigErrorRecoveryEnableDebugLog)
void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) {
{ if (g.ErrorFirst)
ImGuiContext& g = *ctx; IMGUI_DEBUG_LOG_ERROR("[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\n",
ImGuiWindow* window = g.CurrentWindow; g.IO.ConfigErrorRecoveryEnableAssert, g.IO.ConfigErrorRecoveryEnableDebugLog, g.IO.ConfigErrorRecoveryEnableTooltip);
IM_UNUSED(window); IMGUI_DEBUG_LOG_ERROR("[imgui-error] In window '%s': %s\n", window ? window->Name : "NULL", msg);
}
g.ErrorFirst = false;
// Window stacks // Output to tooltip
// NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) if (g.IO.ConfigErrorRecoveryEnableTooltip)
IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!"); {
if (BeginErrorTooltip())
{
if (g.ErrorCountCurrentFrame < 20)
{
Text("In window '%s': %s", window ? window->Name : "NULL", msg);
if (window && (!window->IsFallbackWindow || window->WasActive))
GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 0, 0, 255));
}
if (g.ErrorCountCurrentFrame == 20)
Text("(and more errors)");
// EndFrame() will amend debug buttons to this window, after all errors have been submitted.
EndErrorTooltip();
}
g.ErrorCountCurrentFrame++;
}
#endif
// Global stacks // Output to callback
// For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. if (g.ErrorCallback != NULL)
IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!"); g.ErrorCallback(&g, g.ErrorCallbackUserData, msg);
IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!");
IM_ASSERT(SizeOfDisabledStack == g.DisabledStackSize && "BeginDisabled/EndDisabled Mismatch!"); // Return whether we should assert
IM_ASSERT(SizeOfItemFlagsStack >= g.ItemFlagsStack.Size && "PushItemFlag/PopItemFlag Mismatch!"); return g.IO.ConfigErrorRecoveryEnableAssert;
IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!");
IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!");
IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!");
IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!");
} }
void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip()
@ -10625,6 +10656,21 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip()
g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage"); g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage");
EndErrorTooltip(); EndErrorTooltip();
} }
if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame
{
Separator();
Text("(Hold CTRL and:");
SameLine();
if (SmallButton("Enable Asserts"))
g.IO.ConfigErrorRecoveryEnableAssert = true;
//SameLine();
//if (SmallButton("Hide Error Tooltips"))
// g.IO.ConfigErrorRecoveryEnableTooltip = false; // Too dangerous
SameLine(0, 0);
Text(")");
EndErrorTooltip();
}
#endif #endif
} }

21
imgui.h
View File

@ -29,7 +29,7 @@
// Library Version // Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
#define IMGUI_VERSION "1.91.3 WIP" #define IMGUI_VERSION "1.91.3 WIP"
#define IMGUI_VERSION_NUM 19122 #define IMGUI_VERSION_NUM 19123
#define IMGUI_HAS_TABLE #define IMGUI_HAS_TABLE
/* /*
@ -2265,6 +2265,23 @@ struct ImGuiIO
// Debug options // Debug options
//------------------------------------------------------------------ //------------------------------------------------------------------
// Options to configure how we handle recoverable errors [EXPERIMENTAL]
// - Error recovery is not perfect nor guaranteed! It is a feature to ease development.
// - Functions that support error recovery are using IM_ASSERT_USER_ERROR() instead of IM_ASSERT().
// - You not are not supposed to rely on it in the course of a normal application run.
// - Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers.
// - Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API calls!
// Otherwise it would severely hinder your ability to catch and correct mistakes!
// Read https://github.com/ocornut/imgui/wiki/Error-Handling for details about typical usage scenarios:
// - Programmer seats: keep asserts (default), or disable asserts and keep error tooltips (new and nice!)
// - Non-programmer seats: maybe disable asserts, but make sure errors are resurfaced (visible log entries, use callback etc.)
// - Recovery after error from scripting language: record stack sizes before running script, disable assert, trigger breakpoint from ErrorCallback, recover with ErrorRecoveryTryToRecoverState(), restore settings.
// - Recovery after an exception handler: record stack sizes before try {} block, disable assert, set log callback, recover with ErrorRecoveryTryToRecoverState(), restore settings.
bool ConfigErrorRecovery; // = true // Enable error recovery support. Some errors won't be detected and lead to direct crashes if recovery is disabled.
bool ConfigErrorRecoveryEnableAssert; // = true // Enable asserts on recoverable error. By default call IM_ASSERT() when returning from a failing IM_ASSERT_USER_ERROR()
bool ConfigErrorRecoveryEnableDebugLog; // = true // Enable debug log output on recoverable errors.
bool ConfigErrorRecoveryEnableTooltip; // = true // Enable tooltip on recoverable errors. The tooltip include a way to enable asserts if they were disabled.
// Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro. // Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro.
// - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability. // - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability.
// - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application. // - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application.
@ -2293,7 +2310,7 @@ struct ImGuiIO
bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)
//------------------------------------------------------------------ //------------------------------------------------------------------
// Platform Functions // Platform Identifiers
// (the imgui_impl_xxxx backend files are setting those up for you) // (the imgui_impl_xxxx backend files are setting those up for you)
//------------------------------------------------------------------ //------------------------------------------------------------------

View File

@ -545,6 +545,22 @@ void ImGui::ShowDemoWindow(bool* p_open)
ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors."); ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors.");
ImGui::Text("Also see Style->Rendering for rendering options."); ImGui::Text("Also see Style->Rendering for rendering options.");
// Read https://github.com/ocornut/imgui/wiki/Error-Handling
ImGui::SeparatorText("Error Handling");
ImGui::Checkbox("io.ConfigErrorRecovery", &io.ConfigErrorRecovery);
ImGui::SameLine(); HelpMarker(
"Options to configure how we handle recoverable errors.\n"
"- Error recovery is not perfect nor guaranteed! It is a feature to ease development.\n"
"- You not are not supposed to rely on it in the course of a normal application run.\n"
"- Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers.\n"
"- Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API call!"
"Otherwise it would severely hinder your ability to catch and correct mistakes!");
ImGui::Checkbox("io.ConfigErrorRecoveryEnableAssert", &io.ConfigErrorRecoveryEnableAssert);
ImGui::Checkbox("io.ConfigErrorRecoveryEnableDebugLog", &io.ConfigErrorRecoveryEnableDebugLog);
ImGui::Checkbox("io.ConfigErrorRecoveryEnableTooltip", &io.ConfigErrorRecoveryEnableTooltip);
if (!io.ConfigErrorRecoveryEnableAssert && !io.ConfigErrorRecoveryEnableDebugLog && !io.ConfigErrorRecoveryEnableTooltip)
io.ConfigErrorRecoveryEnableAssert = io.ConfigErrorRecoveryEnableDebugLog = io.ConfigErrorRecoveryEnableTooltip = true;
ImGui::SeparatorText("Debug"); ImGui::SeparatorText("Debug");
ImGui::Checkbox("io.ConfigDebugIsDebuggerPresent", &io.ConfigDebugIsDebuggerPresent); ImGui::Checkbox("io.ConfigDebugIsDebuggerPresent", &io.ConfigDebugIsDebuggerPresent);
ImGui::SameLine(); HelpMarker("Enable various tools calling IM_DEBUG_BREAK().\n\nRequires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application."); ImGui::SameLine(); HelpMarker("Enable various tools calling IM_DEBUG_BREAK().\n\nRequires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application.");

View File

@ -132,6 +132,7 @@ struct ImGuiContext; // Main Dear ImGui context
struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine
struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum)
struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum
struct ImGuiErrorRecoveryState; // Storage of stack sizes for error handling and recovery
struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup()
struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box
struct ImGuiInputTextDeactivateData;// Short term storage to backup text of a deactivating InputText() while another is stealing active id struct ImGuiInputTextDeactivateData;// Short term storage to backup text of a deactivating InputText() while another is stealing active id
@ -148,7 +149,6 @@ struct ImGuiOldColumnData; // Storage data for a single column for lega
struct ImGuiOldColumns; // Storage data for a columns set for legacy Columns() api struct ImGuiOldColumns; // Storage data for a columns set for legacy Columns() api
struct ImGuiPopupData; // Storage for current popup stack struct ImGuiPopupData; // Storage for current popup stack
struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file
struct ImGuiStackSizes; // Storage of stack sizes for debugging/asserting
struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it
struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabBar; // Storage for a tab bar
struct ImGuiTabItem; // Storage for a tab item (within a tab bar) struct ImGuiTabItem; // Storage for a tab item (within a tab bar)
@ -1253,9 +1253,10 @@ struct ImGuiTreeNodeStackData
ImRect NavRect; // Used for nav landing ImRect NavRect; // Used for nav landing
}; };
// sizeof() = 18 // sizeof() = 20
struct IMGUI_API ImGuiStackSizes struct IMGUI_API ImGuiErrorRecoveryState
{ {
short SizeOfWindowStack;
short SizeOfIDStack; short SizeOfIDStack;
short SizeOfColorStack; short SizeOfColorStack;
short SizeOfStyleVarStack; short SizeOfStyleVarStack;
@ -1266,18 +1267,16 @@ struct IMGUI_API ImGuiStackSizes
short SizeOfBeginPopupStack; short SizeOfBeginPopupStack;
short SizeOfDisabledStack; short SizeOfDisabledStack;
ImGuiStackSizes() { memset(this, 0, sizeof(*this)); } ImGuiErrorRecoveryState() { memset(this, 0, sizeof(*this)); }
void SetToContextState(ImGuiContext* ctx);
void CompareWithContextState(ImGuiContext* ctx);
}; };
// Data saved for each window pushed into the stack // Data saved for each window pushed into the stack
struct ImGuiWindowStackData struct ImGuiWindowStackData
{ {
ImGuiWindow* Window; ImGuiWindow* Window;
ImGuiLastItemData ParentLastItemDataBackup; ImGuiLastItemData ParentLastItemDataBackup;
ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting
bool DisabledOverrideReenable; // Non-child window override disabled flag bool DisabledOverrideReenable; // Non-child window override disabled flag
}; };
struct ImGuiShrinkWidthItem struct ImGuiShrinkWidthItem
@ -1885,12 +1884,17 @@ struct ImGuiLocEntry
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Macros used by Recoverable Error handling // Macros used by Recoverable Error handling
// Down the line in some frameworks/languages we would like to have a way to redirect those to the programmer and recover from more faults. // - Only dispatch error if _EXPR: evaluate as assert (similar to an assert macro).
// - The message will always be a string literal, in order to increase likelihood of being display by an assert handler.
// - The intent is that you may rewire this macro to dispatch dynamically:
// - On programmers machines, when debugger is attached, on direct imgui API usage error: always assert!
// - On exception recovery and script language recovery: you may decide to error log.
#ifndef IM_ASSERT_USER_ERROR #ifndef IM_ASSERT_USER_ERROR
#define IM_ASSERT_USER_ERROR(_EXPR,_MSG) IM_ASSERT((_EXPR) && _MSG) #define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0) // Recoverable User Error
#endif #endif
typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...);
typedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const char* msg); // Function signature for g.ErrorCallback
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] Metrics, Debug Tools // [SECTION] Metrics, Debug Tools
@ -2313,8 +2317,14 @@ struct ImGuiContext
int LogDepthToExpand; int LogDepthToExpand;
int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call.
// Error handling // Error Handling
ImGuiErrorCallback ErrorCallback; // = NULL. May be exposed in public API eventually.
void* ErrorCallbackUserData; // = NULL
ImVec2 ErrorTooltipLockedPos; ImVec2 ErrorTooltipLockedPos;
bool ErrorFirst;
int ErrorCountCurrentFrame; // [Internal] Number of errors submitted this frame.
ImGuiErrorRecoveryState StackSizesInNewFrame; // [Internal]
ImGuiErrorRecoveryState*StackSizesInBeginForCurrentWindow; // [Internal]
// Debug Tools // Debug Tools
// (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.)
@ -3419,9 +3429,11 @@ namespace ImGui
IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window);
// Error handling, State Recovery // Error handling, State Recovery
IMGUI_API bool ErrorLog(const char* msg);
IMGUI_API void ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out);
IMGUI_API void ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in);
IMGUI_API void ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in);
IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
IMGUI_API void ErrorCheckEndFrameRecover();
IMGUI_API void ErrorCheckEndWindowRecover();
IMGUI_API void ErrorCheckEndFrameFinalizeErrorTooltip(); IMGUI_API void ErrorCheckEndFrameFinalizeErrorTooltip();
IMGUI_API bool BeginErrorTooltip(); IMGUI_API bool BeginErrorTooltip();
IMGUI_API void EndErrorTooltip(); IMGUI_API void EndErrorTooltip();

View File

@ -1484,7 +1484,9 @@ void ImGui::EndTable()
{ {
short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask; short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask;
inner_window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; // So empty table don't appear to navigate differently. inner_window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; // So empty table don't appear to navigate differently.
g.CurrentTable = NULL; // To avoid error recovery recursing
EndChild(); EndChild();
g.CurrentTable = table;
inner_window->DC.NavLayersActiveMask = backup_nav_layers_active_mask; inner_window->DC.NavLayersActiveMask = backup_nav_layers_active_mask;
} }
else else

View File

@ -7551,7 +7551,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
ImGuiMultiSelectState* storage = ms->Storage; ImGuiMultiSelectState* storage = ms->Storage;
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(ms->FocusScopeId == g.CurrentFocusScopeId); IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!");
IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow); IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);
IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect); IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);