VesCodes / ImGui

ImGui plugin for Unreal Engine
MIT License
107 stars 15 forks source link

Proper way to do user-side context initialization? #6

Closed Wyverex42 closed 7 months ago

Wyverex42 commented 7 months ago

I've updated to latest according to https://github.com/VesCodes/ImGui/issues/4#issuecomment-1902678625 and I was going to remove my change to ImGuiContext.cpp to add new fonts when I realized that I still don't really know where to put those AddFont calls instead. Ideally this would have to be done when the context is first created but that happens in response to the Slate widget being created and that happens the first time an FScopedContext is used, so it could be anywhere.

Do you have a suggestion where to put this? Feels like either the ImGuiModule needs some customization where you can supply a delegate that's called when the context is first created, or I need to create the context explicitly at some well-known point on startup. However, that could be tricky itself because I would have to carefuly manage the lifetime of that context until the Slate widget has been created, which doesn't feel ideal.

Any ideas?

VesCodes commented 7 months ago

With the fix to #4 I exposed FImGuiContext::BeginFrame and FImGuiContext::EndFrame which allows you to interrupt the frame:

const ImGui::FScopedContext ScopedContext;
if (ScopedContext.IsValid())
{
    ScopedContext->EndFrame();
    ImGui::GetIO().Fonts->AddFontFromFileTTF(...);
    ScopedContext->BeginFrame();
}

I am considering adding some callbacks to inject custom logic at certain points such as context initialisation. Other than backend and platform configuration which I would have kind of liked to keep private, is there anything else that can only be done safely prior to the first frame where this interrupt won't quite cut it?

Alternatively - and this might be subject to change so I'd recommend the above method - it's worth highlighting that currently ImGui rendering doesn't start until the next frame after a context has been created. Not quire sure if I'll retain this behaviour, though I don't foresee it being an issue (what are your thoughts?). Somewhat confusingly there are two different "readiness" checks for scoped contexts:

So in short the first time a scoped context is created you could check if (ScopedContext.IsValid()) and do some post-creation initialisation as if (ScopedContext) blocks will be skipped.

Wyverex42 commented 7 months ago

Other than backend and platform configuration which I would have kind of liked to keep private, is there anything else that can only be done safely prior to the first frame where this interrupt won't quite cut it?

I'm not sure tbh. I'm not an ImGui expert, so I'm discovering stuff as I need it :)

it's worth highlighting that currently ImGui rendering doesn't start until the next frame after a context has been created

I don't think that's going to be an issue for us.

So in short the first time a scoped context is created

I think the issue with this approach is that you have to know when the first context is being created to perform initialization. We have contexts in multiple places in our code so any of these could be the first one. In practice it's probably fair to assume that only a handful of these could be the first but it's still a bit ambiguous.

To use this pattern we'd have to find a place that's guaranteed to be executed first but is still late enough in the initialization cycle to not cause problems. I tried to do initialization in our main module last week but that created issues in editor builds. I'll have another look later this week

VesCodes commented 7 months ago

I don't see how an initialization delegate would solve this issue though, since you'll still need to hook it prior to the context being created anyway. In essence it's no different than the second solution outlined in my last reply.

I tested adding extra fonts at a late point by interrupting the ImGui frame as outlined in the first solution and saw no issues, did you try that out?

Wyverex42 commented 7 months ago

Thanks, that approach worked out indeed!

For posterity, I ended up having to do this, since it's not guaranteed that the scoped context evaluates to true on the first run:

        const ImGui::FScopedContext ScopedContext;
        if (ScopedContext)
        {
            ScopedContext->EndFrame();
            SetupFonts();
            ScopedContext->BeginFrame();
        }
        else
        {
            SetupFonts();
        }