Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context recovery / graceful handling of bad state #1651

Closed
sherief opened this issue Feb 27, 2018 · 50 comments
Closed

Context recovery / graceful handling of bad state #1651

sherief opened this issue Feb 27, 2018 · 50 comments

Comments

@sherief
Copy link

sherief commented Feb 27, 2018

I'm using vanilla Dear Imgui v1.60 WIP.

I have an app that exposes imgui API to plugins using a scripting language. Now, in my own code I can be sure that I am "well-behaved" - for example, I match calls to ImGui::Begin() with calls to ImGui::End(). But I can't be sure that plugins will be the same. I also have live reloading of plugin scripts, and sometimes I end up trying to load plugin code that won't compile (I can keep using the last-known-good version in this case) or worse: plugin code that isn't well-behaved with respect to imgui API. Consider a plugin that consists entirely of [the scrip equivalent of] the following:

ImGui::Begin("This won't end well.");

I end up hitting this assertion:

IM_ASSERT(g.CurrentWindowStack.Size == 1);    // Mismatched Begin()/End() calls

As a feature request, is it possible to consider supporting more graceful failures? I don't have a solid idea in mind, but you can see my use case above. It could be something like D3D12's command lists, where the CloseCommandList() function returns a status code indicating whether this was a valid or malformed command list - maybe ImGui::EndFrame() could return a similar value and just drop the frame altogether, or render as much of it as it can [and I realize how this is a fuzzy concept - I'm just thinking out loud here].

Is there some other way to do this that I'm missing? I wouldn't mind running the plugin code in its own imgui context (multiple context support rocks!), but currently that doesn't seem to solve the problem I have.

@ocornut
Copy link
Owner

ocornut commented Feb 27, 2018

I agree this is desirable, I'll have to think about how we can do it but there's definitively improvements to make.

One of the problem is that several of those issues ideally wants some reporting, and for more local push/pop it is nice to be able to notify the programmer locally.

I'm also not really sure we want imgui to be one of those library that spams you with warnings and log (whether they are printf or a specific window for that purpose!). Even if that's hidden behind some sort of flag, it typically end up being either enabled on all machines and spammy, or disabled and unnecessary, so it's a little tricky to find the right balance. If you have any suggestion..

@sherief
Copy link
Author

sherief commented Feb 27, 2018

I'm not a fan of having the library spam output anywhere - how about having End() take a custom user function like this:

using handler = HandlerResponse (*)(Context* context, /* args for error type and description */, void* userData);

ImGui::End(handler CustomHandler = ImGui::DefaultHandler, void* userData /*passed as-is to handler */ = nullptr);

This way, code is backwards-compatible. If you don't specify a custom handler, the default handler can call IM_ASSERT(). The code also won't have to implement writing anything anywhere - that's the handler's job. And HandlerResponse can communicate info back to imgui (maybe what to do on failure - drop entire frame vs attempt to recover as much UI as you can?).

What do you think?

@ocornut
Copy link
Owner

ocornut commented Feb 27, 2018

I'll have to experiment with Begin/End, Push/Pop variants and first see how we can reliably detect and recover.

Another kind of imgui issue that may be interesting to detect are ID collisions, which can happen anywhere. For those if we want a lightweight check we'll have to do it when either widget is active.

This might end up getting similar to just rewiring IM_ASSERT() when you are in the context of your scripting language, assuming imgui can recover after said assert.

@sherief
Copy link
Author

sherief commented Feb 27, 2018

Would it be possible to save / load the context's state to a chunk of memory? I'm thinking of something like

void* buffer = malloc();
ImGui::SaveState(buffer, bufferSize);
bool success = runScriptUICode();
if(!success)
{
ImGui::LoadState(buffer, bufferSize); //Back where we started
}
ImGui::EndFrame();

@ocornut
Copy link
Owner

ocornut commented Feb 27, 2018

Not really, but just destroying and recreating a context generally yield good result because the windows position/size are saved. You lose things like tree node open/close state but otherwise it's not really visible.

@sherief
Copy link
Author

sherief commented Feb 27, 2018

That's workable. Ideally though, being able to save the state once the UI code gets "borked" then returning to it once it's unborked would be great. Some of the plugin code operates on a DCC-like app's object hierarchy and tree node state specifically is valuable to me.

Not to mention, this might also help with the case of restoring UI state across launches which is another issue I was planning to bring up at one point...

@ocornut
Copy link
Owner

ocornut commented Feb 28, 2018

Not to mention, this might also help with the case of restoring UI state across launches which is another issue I was planning to bring up at one point...

Depend what you mean by UI state:

Tree nodes/Collapsing header open/close state
Technically you can use SetStateStorage()/GetStateStorage() (per-window) and backup/restore that content however it REALLY wasn't designed for persistence: there's no type info/safety, no handling of hash collision, no clean up/garbage collection, So while it works well enough for typical working session (and nobody reported an issue) if you were to store and restore that data long term it would probably build-up into a problem over time.

I don't think you really want to be preserving most of tree node state but it may be useful to certain certain nodes for saving. If we end up reorganizing the state storage field we could add enough info. Currently the struct is:

u32 pair
void* value (enum)

We could stick in a few things, e.g.

u32 active_this_session : 1
u32 persistence_desirable : 1
u32 type : remaining bits?

Haven't really thought about it more than that.

Z-order
I agree it's a problem that it's not preserved for now.

What else?
Most of the data is on your side. It would be nice if we provided im-style very basic serialization primitives, but with persistence comes a bunch of new responsibility and I don't see it being a near-term priority.

I hope in the upcoming few months I can tackle more graceful error handling tho.

@sherief
Copy link
Author

sherief commented Mar 1, 2018

Man I don't even know what all state involved in a restore is, but I'm setting out to find out now.

@nikki93
Copy link

nikki93 commented May 21, 2018

@ocornut -- Did you get a chance to think about / tackle this more? I'm exposing ImGui in a live scripting context so it's common that there are mismatches when the code a user is writing isn't 'complete' yet due to try/catch style behavior in the scripting language. I have a workaround for now by having my own utilities that take and call a closure and make sure to do the .end*()/.pop*() calls, allowing code like:

imgui.inWindow(..., function()
  imgui.selectable(...)
  -- Can do stuff throwing errors here, will still call `.end()` after
  error('oops!')
end)

Again, sorry for using a non-C++ language to show this example but the point was to show the utility allowing safe calls that I wrote in the scripting language. :)

Because I have these utilities it's not super urgent for me, but wondering if you've thought further.

@sherief
Copy link
Author

sherief commented May 21, 2018

That's exactly the case I'm running into. I have a live reload system in place and sometimes a script is written to disk with a logic error somewhere, and on the next live reload my UI state goes FUBAR.

I've resorted to some state tracking similar to what you have, but it's fragile, doesn't cover all cases, and feels hacky af.

@ocornut
Copy link
Owner

ocornut commented May 21, 2018

Sorry I haven't had time to look at that yet. It looks sufficiently simple and useful that I may. I think it possibly might make more sense as an explicit helper to be called optionally (and generally by user of scripting languages) rather than being hard-wired by default.

The reason being that we can't just silently ignore those errors. The expectation is that the code running script would poll an error flag and turn this into end-user feedback, however at the library/C++ level we have no way of providing this feedback to the user. Making this explicit also solve the issue that scope are not easy to define (e.g. the issue with PushStyleCol/Begin/PopStyleCol/End patterns, see #1767).

Even though they are less common, we would nice to also protect from excessive calls to PopXXX/EndXXX functions. Please understand that this never will be a 100% sandboxing, are we in agreement that this would be designed to minimize the side-effect and cost of programmer errors, but not to protect from malicious users?

@nikki93
Copy link

nikki93 commented May 21, 2018

@ocornut — I’m in full agreement with all of this! Yeah, not meant to be a security feature at all, just convenience at most (in my case). One little question: Would the error polling just show most recent error or collect errors since the last poll? For GL calls for example it only collects last one which makes one need to call glError(...) nearly after every call in a user-friendly wrapper. Accumulating errors would remove this overhead. In any case, having anything at all in this realm would be a great improvement so I’m open either way.

@sherief
Copy link
Author

sherief commented May 21, 2018

Yes, this shouldn't be intended to protect from malice, just misuse. If you're dealing with a malicious user you'll need to have bindings that apply enough mitigation to have some sort of measurable impact, and that doesn't belong in imgui - people who want such mitigations can implement them on top of the public imgui API just fine.

@ejulien
Copy link

ejulien commented Nov 22, 2018

Hello,

Context: Our editor can be extended using plugins. Plugin hot-reload+ImGui is extremely fast to iterate with.


Problem: Unfortunately API misuses are really ruining our party. These come in two flavors:

  1. Genuine errors (eg. wrongly scoped End or EndChild call).
  2. Script errors interrupting an otherwise valid sequence of ImGui calls.

Both usually end with a C++ assertion being thrown which is less than ideal.


Solution?: Both scenarios can be adressed in the same manner but solving the first one might be more work.

Handling scenario 2. could look something like:

ImGui::EnterUntrustedSection();

bool success = call_plugin_issuing_ImGui_calls());
if (!success)
    ImGui::RollbackUntrustedSection();

ImGui::ExitUntrustedSection();

if (!success)
    ImGui::Text("So sad");

Script execution failure was reported by the client code but that doesn't cover API misuse.


The ideal version would validate the untrusted section on exit and roll it back if it did not pass.

ImGui::EnterUntrustedSection();

call_plugin_issuing_ImGui_calls(); // alea jacta est

if (!ImGui::ExitUntrustedSection())
    ImGui::Text("So sad");

Obviously logic already executed in the client code can not be rolled back (eg. if (ImGui::Button) ++my_var;) but that really doesn't matter. As soon as the error is repaired the UI will correctly reflect those changes anyway.


While not a critical feature to have I wonder how hard it would be to implement on top of the current design?

@ejulien
Copy link

ejulien commented Nov 22, 2018

Seeing that #2096 is cross-referenced here might I add that I would be much more interested in completely rolling back a broken declaration rather than having a partial declaration automatically rendered valid.

I'd rather present the user a known-to-be-working UI of my choosing in case of a failure.

@ocornut
Copy link
Owner

ocornut commented Nov 22, 2018

There's no magic "rolling back" we have to address specific issues one by one, clarify the fail cases and what we want to do out of them. It probably boils down to clearing most stacks.

E.g. We could merely aim at making EndFrame() fail proof and then if you have an issue on your side you just call some form of EndFrame() that is safe to call and move on to the next frame (you may choose to not render this frame or not).

The problem is that genuine error needs to be reported somehow, we can't have a fully silent form of error handling.

@ejulien
Copy link

ejulien commented Nov 22, 2018

Precise error reporting would be nice to have and overriding IM_ASSERT to report errors seems good. This together with a safe EndFrame would solve most issues a script writer can face if not the cosmetic ones.

However, I don't really understand your objection to the magic "roll back", is it on a technical standpoint? It looks like the most foolproof approach to me.

@ocornut
Copy link
Owner

ocornut commented Nov 22, 2018

However, I don't really understand your objection to the magic "roll back", is it on a technical standpoint? It looks like the most foolproof approach to me.

I don't know what "rollback" means other than taking a snapshot of the entire memory used by dear imgui and restoring that, complete with pointers provided by the user-allocator (so yeah, it is a technical problem to implement).

ocornut added a commit that referenced this issue Dec 23, 2018
…ght by an assert in the End() function itself at the call site (instead of being reported in EndFrame). Past the assert, they don't lead to crashes any more. Missing calls to End(), pass the assert, should not lead to crashes any more, nor to the fallback/debug window appearing on screen. (#1651).
@ocornut
Copy link
Owner

ocornut commented Dec 23, 2018

I made some changes so that mismatched Begin/End calls should not lead to crashes or other side effects any more. However note that they will still trigger IM_ASSERT() ( and I have improved the formulation and location of those asserts). For scripting language it is expected that you teach your assert handler to play nice with the app (e.g. error/log/abort script?).

While this doesn't cover everything I believe this should cover MOST errors, and would be interested to know if it works better for you. @sherief , @ejulien , etc.

ocornut added a commit that referenced this issue Jan 2, 2019
…en showing the CTRL+Tab list and or fallback "...." tooltip.
ocornut added a commit that referenced this issue Jan 2, 2019
…ssert when showing the CTRL+Tab list and or fallback "...." tooltip."

This reverts commit 1b0e38d.
ocornut added a commit that referenced this issue Jan 2, 2019
ocornut added a commit that referenced this issue Feb 22, 2019
… mis-usage don't lead to hard crashes any more, facilitating integration with scripting languages. (#1651)
@rokups
Copy link
Contributor

rokups commented Oct 10, 2019

Note for the future: just found out it does not cover ImGui::TreePop().

ocornut added a commit that referenced this issue Sep 20, 2024
… Added helper default callback. Comments. (#1651)

(doesn't affect test engine hook for it as trailing \n are trimmed anyhow)
ocornut added a commit that referenced this issue Sep 23, 2024
@ocornut
Copy link
Owner

ocornut commented Sep 24, 2024

I'm working on a few things
error_handling 2

The great bit is that if you use ImGuiErrorFlags_NoAssert you can get away with a tooltip:

#if 0
    if (ImGui::Begin("Error 1"))
    {
        ImGui::Text("This window calls End() inside the if (Begin()) block!");
        ImGui::End();
    }
    else
    {
        printf("");
    }
#endif
#if 1
    if (ImGui::Begin("Error 2"))
    {
        ImGui::Text("This window calls PushID() without popping!");
        ImGui::PushID("Hello");
    }
    ImGui::End();

Edit:final API/names are different but essentially similar features.

// Macros used by Recoverable Error handling
// - 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
#define IM_ASSERT_USER_ERROR(_EXPR,_MSG)    do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0)    // Recoverable User Error
#endif

// - Error recovery is provided as a way to facilitate recovery from errors in e.g. scripting languages, or after specific exceptions handlers.
// - Error recovery is not perfect nor guaranteed! You are not supposed to rely on it in the course of a normal application run.
// - Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API call,
//   and especially in native code. Otherwise it would severely hinder your ability to catch and correct mistakes!
// - (1) Programmer seats: default path is:
//   - Recoverable errors will call IM_ASSERT_USER_ERROR(), leading to IM_ASSERT() being executed.
//   - Recovery will generally be performed, assuming you can resume from your assert.
// - (2) Programmer seats: if you disable Asserts (either via ImGuiErrorFlags_NoAssert either via a disabled IM_ASSERT macros) but keep Tooltips:
//   - Recoverable errors will be visible in a tooltip.
//   - Programmer is prompted to press F1 to enable asserts.
//   - THIS IMPLICITLY RELY ON RECOVERY BEING 100% FUNCTIONAL WHICH IS NOT GUARANTEED. A FEW CASES MIGHT PROBABLY STILL CRASH/ASSERTS.
//   - THIS IMPLICITLY RELY ON 'F1' KEY BEING AVAILABLE WITHOUT CAUSING MUCH HARM IN YOUR APP. // FIXME-ERRORHANDLING: Need to find a solution for this.
// - (3) What you might want to setup on non-programmers seats:
//   - Use ImGuiErrorFlags_NoAssert, but make sure log entries are well visible or reported somewhere.
//   - If errors are not well resurfaced to programmers, it may hinder your ability to prevent errors from staying/spreading in the codebase. Use with caution!
// - (4) If you offer the ability to write Dear ImGui code via e.g. scripting language, you may want to:
//   - Use ErrorRecoveryStoreState() to record stack sizes before running the script interpreter.
//   - Use ImGuiErrorFlags_NoAssert but only while in the context of the script interpreter.
//   - Maybe trigger a script break from your ErrorCallback if you can.
//   - Ensure that e.g ImGuiErrorFlags_NoTooltip is NOT set, and that log entries are well surfaced and visible somewhere.
// - (5) To handle resuming code execution after an exception:
//   - Use ErrorRecoveryStoreState() to record stack sizes before your try {} block.
//   - Temporary adjust settings (e.g. disable assert, set a log callback).
//   - Call ErrorRecoveryTryToRecoverState()
//   - Restore settings.
// FIXME-ERRORHANDLING: recover may log many times and assert once?
enum ImGuiErrorFlags_
{
    ImGuiErrorFlags_None                = 0,
    ImGuiErrorFlags_EnableRecovery      = 1 << 0,   // Enable recovery attempts. Some errors won't be detected and lead to direct crashes if recovery is disabled.
    ImGuiErrorFlags_NoDebugLog          = 1 << 1,   // Disable debug log on error.
    ImGuiErrorFlags_NoAssert            = 1 << 2,   // Disable asserts on error. Otherwise: we call IM_ASSERT() when returning from a failing IM_ASSERT_USER_ERROR()
    ImGuiErrorFlags_NoTooltip           = 1 << 3,   // Disable tooltip on error. The tooltip will display a keyboard shortcut to enable asserts if they were disabled!
};

typedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const char* msg);
// Error Handling [EXPERIMENTAL]
// (this will eventually be moved to public ImGuiIO when we are happy with the feature)
ImGuiErrorFlags         ErrorFlags;                             // = Assert+DebugLog+Tooltip. See ImGuiErrorHandlingFlags_
ImGuiErrorCallback      ErrorCallback;                          // = NULL
void*                   ErrorCallbackUserData;                  // = NULL

Branch https://github.com/ocornut/imgui/compare/features/error_recovery?expand=1

I need to stress that it is unlikely that recovery is going to be 100% perfect all the time but I believe if it handles 99% of realistic error cases then we may be good. This is not merged yet. Curious if you have feedback on whether this can be useful to your case.

The only feature "regression" is that if you are to disable _EnableRecovery it doesn't get you exactly the same asserts as before.

I believe if the feature works it should be a non-issue, but I would need to see how it behave in real world vs my few tests.

(I think I may simultaneously start working on official scoped/raii helpers so people have them in a a separate header, even if I don't fancy them)

@Web-eWorks
Copy link

Web-eWorks commented Sep 25, 2024

One additional concern for your consideration is that the recovery feature may also be usable / extremely useful in a "graceful exit" scenario in the presence of external error handling.

We're currently using a slightly patched and integrated version of ImGui 1.89.8's ErrorCheckEndWindowRecover in Pioneer Space Sim as a way to ensure ImGui is left in a sane state if Lua code (the main user of the ImGui API) throws an error; when entering a protected call an ImGuiStackSizes object is filled and saved and if an error is thrown the error handler unwinds the various ImGui stacks.

Of note, we've had to patch ImGuiStackSizes to add an additional SizeOfTabBarStack variable - from my off-hand skimming at the moment I think the error handling code in 1.89.8 will produce an invalid result if running ErrorCheckEndWindowRecover in a child window nested inside a tab bar item. We also recover the IDStack to its saved state rather than a depth of 1, and I suspect the TreeDepth state also should be saved and recovered for completeness.

The proposed TryToRecoverWindowState function I believe would be extremely useful to this specific use case, in addition to the other changes in that branch - we'd definitely benefit from the ability to have IM_ASSERT_USER_ERROR log errors rather than remaining unhandled and leading to a segfault at worst.

ocornut added a commit that referenced this issue Sep 25, 2024
@ocornut
Copy link
Owner

ocornut commented Sep 26, 2024

"graceful exit" scenario in the presence of external error handling.
We're currently using a slightly patched and integrated version of ImGui 1.89.8's ErrorCheckEndWindowRecover in Pioneer Space Sim as a way to ensure ImGui is left in a sane state if Lua code (the main user of the ImGui API) throws an error; when entering a protected call an ImGuiStackSizes object is filled and saved and if an error is thrown the error handler unwinds the various ImGui stacks.

This is one of the scenario that I my changes are aimed to support, see block (4) in my comments.

1.89.8 will produce an invalid result if running ErrorCheckEndWindowRecover in a child window nested inside a tab bar item

This was fixed by bc77041 a few days ago (before I made my post).

We also recover the IDStack to its saved state rather than a depth of 1,

Right, this was a mistake.

and I suspect the TreeDepth state also should be saved and recovered for completeness.

Added too.

I will soon push my work on this. When done I would appreciate if you can update your copy and try using the new functions.

@Web-eWorks
Copy link

This is one of the scenario that I my changes are aimed to support, see block (4) in my comments.

I've now given the linked branch a more full read-through and I think the proposed API would neatly replace our adaptation in its entirety - I'm very glad to see this feature be proposed as a native/upstream functionality.

I will soon push my work on this. When done I would appreciate if you can update your copy and try using the new functions.

I would be very happy to! It might take me a little bit - upgrading the ImGui version used by Pioneer is not entirely trivial, but once we've migrated to v1.9.2 it should be easy to integrate the proposed branch and test against our usecase.

ocornut added a commit that referenced this issue Sep 27, 2024
… EndDisabled(), PopTextWrapPos(), PopFocusScope(), PopItemWidth() to use IM_ASSERT_USER_ERROR(). (#1651, #5654)
ocornut added a commit that referenced this issue Sep 27, 2024
…s to IM_ASSERT_USER_ERROR(). (#1651, #5654)

This commit is not meant to be functional as-is (it will break test engine recovery). This is mostly to reduce/remove noise from upcoming commits.
ocornut added a commit that referenced this issue Sep 27, 2024
Setup a couple of features to configure them, including ways to display error tooltips instead of assserting.
@ocornut
Copy link
Owner

ocornut commented Sep 27, 2024

I have pushed this to master and docking now.
The main commit is 30c29d2
I am expecting further iterations.
In the current version, with default setup, most people won't see a difference as the default include enabled asserts.

However, the first time an error is logged, since the default including debug/tty logging, it'll print the "current settings" which is one way which should start directing users to new features:

ImGui::Begin("Oops!");
[00001] [imgui-error] (current settings: Assert=1, Log=1, Tooltip=1)
[00001] [imgui-error] In window 'Oops!': Missing End()
Assertion failed: (0) && "Missing End()", file c:\omar\work\imgui\imgui.cpp, line 10501

I started working on a new https://github.com/ocornut/imgui/wiki/Error-Handling page.

image

image

@ocornut
Copy link
Owner

ocornut commented Sep 27, 2024

I invite all people interested in this to try it out and make feedback, with specific repros for ways we can improve it.
AFAIK the bulk of what's discussed here is supported so I will close this for now.

API for recovery is in imgui_internal.h:

ImGuiErrorRecoveryState state;
try
{
   ImGui::ErrorRecoveryStoreState(&state);
.... run code
}
catch
{
   ImGui::ErrorRecoveryTryToRecoverState(&state);
}

Chosen name "Try to recover" over e.g. "Restore" to suggest this is not a 100% guaranteed recovery.

Public stuff:

// 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.

The ErrorCallback is still private, as I expect it will be used by the same population as one using ErrorRecoveryTryToRecoverState().

@Mellnik
Copy link

Mellnik commented Sep 27, 2024

I am sorry but in your example snippet the recovery happens inside a try/catch. Does this also work for applications without exception handling or RTTI?

@ocornut
Copy link
Owner

ocornut commented Sep 27, 2024 via email

ocornut added a commit that referenced this issue Sep 30, 2024
RobRoyEerkes pushed a commit to RobRoyEerkes/imgui that referenced this issue Oct 2, 2024
@Mellnik
Copy link

Mellnik commented Oct 12, 2024

Is this still the correct issue to post on?

Maybe I am mistaken but I don't see a proper way for support scripting languages. For example calling IsItemHovered with wrong flags will result in an IM_ASSERT.

bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
{
    ImGuiContext& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;
    IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0 && "Invalid flags for IsItemHovered()!");
....

As I understand IM_ASSERT is not supposed to be recoverable? Meaning you can not use ImGui with user scripts as it would result in an assert/crash.

Where is the main difficulty for properly supporting scripting languages? By that I mean exposing draw functions to scripts but of course not internal/backend functions like NewFrame.

Thank you.

@nicolasnoble
Copy link
Contributor

nicolasnoble commented Oct 12, 2024

I would argue this one specifically needs to be changed to a user error instead of a hard assert. My impression is there will be some cleanup to do. I have yet to try the change, mind you.

@ocornut
Copy link
Owner

ocornut commented Oct 12, 2024 via email

ocornut added a commit that referenced this issue Oct 14, 2024
@Mellnik
Copy link

Mellnik commented Oct 14, 2024

For applications embedding Lua I might have found a viable solution.

First of all I consider two kind of Dear ImGui functions.

  • Those that do internal/backend work like NewFrame, Render, and everything related to ImGuiContext, ImDrawData, etc.
  • And those which are purely used for drawing like Begin. IsItemHovered, InputText, etc.

Only drawing functions are exposed to Lua guest/user scripts.

The Lua API has a C function named luaL_error which you can call in C functions that are exposed to Lua. This function internally uses the OS API to rewind the entire C stack back to the point of the first lua_pcall. This is basically the point where you call a guest/user script.

The definition IM_ASSERT is being overridden to call a custom callback function.

  • If the application currently executes a script then the said luaL_error is called. This will print an error, rewind the stack, exit the Lua VM and jump back to the point where I called the Lua script in C.
  • If there is no script running then we assume the assert does not come from a script error but rather from an error in the host application itself. In this case the application exits with an assert. If the application was compiled for production then it prints an error and exits gracefully.

Now the only problem since version 1.91.3 is that ErrorCheckEndFrameRecover was moved to ErrorRecoveryTryToRecoverState. So when Dear ImGui tries to recover itself with ErrorRecoveryTryToRecoverState it again throws an IM_ASSERT_USER_ERROR inside of it.

Setting ConfigErrorRecoveryEnableAssert to false is not a solution. EndFrame is called unrelated to script execution, so setting that flag just during script executions has no effect.
Setting it always to false probably is not an option as well, because it seems like you will be using IM_ASSERT_USER_ERROR in other parts of the code base too like IsItemHovered. We still want to catch those later with IM_ASSERT.

With the explained approach it would be good to have a manual recovery option like the old ErrorCheckEndFrameRecover.

Thank you for your time. Looking forward to your thoughts.

@ocornut
Copy link
Owner

ocornut commented Oct 14, 2024

@Mellnik would you mind opening a new issue for this? would be better tracked since it's not a trivial task. Thanks!

@Mellnik
Copy link

Mellnik commented Oct 18, 2024

@ocornut

I have found a solution that works for my request/problem in my previous post. Setting ConfigErrorRecoveryEnableAssert to false for the duration of EndFrame allows me to log possible IM_ASSERT_USER_ERROR but still continue with the recovery operation.

io.ConfigErrorRecoveryEnableAssert = false;
ImGui::EndFrame();
io.ConfigErrorRecoveryEnableAssert = true;

So at least for me I won't need another issue for this. But if you like we can still create a new one for future works on the recovery system?

Like I still wonder the following. You mentioned in an earlier post that not all errors are recoverable. But are all possible errors covered with asserts in the code base?

@ocornut
Copy link
Owner

ocornut commented Oct 18, 2024

(By asking questions or posting self-answer it is essentially asking me to interact which defeat the purpose of create new issues to silo or track things. I'm answering here assuming this is a closure on this topic. It's my fault for not reacting to your first post which nicely actually asked "Is this still the correct issue to post on?").

As per https://github.com/ocornut/imgui/wiki/Error-Handling, the idea is that you can call ErrorRecoveryTryToRecoverState() yourself right after your script execution without waiting for EndFrame(). So you have a finer control of when things are recovered/logged and with which flags. Also useful if you need to differentiate one script from another when handling errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests