Added support for session reloading and updated contexts and widgets management:

- Added to ImGui Context Proxy a name that is mapped to ini file set in ImGui context.
- ImGui Context Manager generates unique context names from world type and context index.
- Refactored ImGui Context Manager to have a cleaner separation between editor and non-editor bits.
- Fixed context update rules in ImGui Context Manager.
- Changed widgets management in ImGui Module Manager to allow automatic garbage collection after viewports are closed and manual removal when module is shutting down.
- ImGui Widgets are in full control of communication with context proxies.
- Added basic world context utilities.
- Refactored world context index utilities and replaced ambiguous 'default context index' with 'editor' and 'game' ones.
This commit is contained in:
Sebastian 2017-09-27 21:16:54 +01:00
parent 7e756c7cb5
commit 1a6aa98f51
13 changed files with 359 additions and 150 deletions

View File

@ -4,68 +4,141 @@
#include "ImGuiContextManager.h"
#include "ImGuiImplementation.h"
#include "Utilities/WorldContext.h"
#include "Utilities/WorldContextIndex.h"
FImGuiContextProxy& FImGuiContextManager::GetWorldContextProxy(UWorld& World)
#include <imgui.h>
namespace
{
const int32 Index = Utilities::GetWorldContextIndex(World);
checkf(Index != Utilities::INVALID_CONTEXT_INDEX, TEXT("Couldn't resolve context index for world %s: WorldType = %d"),
*World.GetName(), World.WorldType);
#if WITH_EDITOR
// Make sure that PIE worlds don't try to use editor context.
checkf(!GEngine->IsEditor() || Index != Utilities::DEFAULT_CONTEXT_INDEX, TEXT("Index for world %s "
"was resolved to the default context index %d, which in editor is reserved for editor context. PIE worlds "
"should use values that start from 1. WorldType = %d, NetMode = %d"), *World.GetName(),
Utilities::DEFAULT_CONTEXT_INDEX, World.WorldType, World.GetNetMode());
// Name for editor ImGui context.
FORCEINLINE FString GetEditorContextName()
{
return TEXT("Editor");
}
// Name for world ImGui context.
FORCEINLINE FString GetWorldContextName(const UWorld& World)
{
using namespace Utilities;
const FWorldContext* WorldContext = GetWorldContext(World);
switch (WorldContext->WorldType)
{
case EWorldType::PIE:
return FString::Printf(TEXT("PIEContext%d"), GetWorldContextIndex(*WorldContext));
case EWorldType::Game:
return TEXT("Game");
case EWorldType::Editor:
return TEXT("Editor");
default:
return TEXT("Other");
}
}
#else
FORCEINLINE FString GetWorldContextName()
{
return TEXT("Game");
}
FORCEINLINE FString GetWorldContextName(const UWorld&)
{
return TEXT("Game");
}
#endif // WITH_EDITOR
}
FContextData& Data = FindOrAddContextData(Index);
// Track worlds to make sure that different worlds don't try to use the same context in the same time.
if (!Data.World.IsValid())
{
Data.World = &World;
}
else
{
checkf(Data.World == &World, TEXT("Two different worlds, %s and %s, resolved to the same world context index %d."),
*Data.World->GetName(), *World.GetName(), Index);
}
return Data.ContextProxy;
FImGuiContextManager::~FImGuiContextManager()
{
Contexts.Empty();
ImGui::Shutdown();
}
void FImGuiContextManager::Tick(float DeltaSeconds)
{
FContextData* Data = Contexts.Find(1);
if (!Data || !Data->World.IsValid())
// In editor, worlds can get invalid. We could remove corresponding entries, but that would mean resetting ImGui
// context every time when PIE session is restarted. Instead we freeze contexts until their worlds are re-created.
for (auto& Pair : Contexts)
{
return;
}
for (auto& Entry : Contexts)
{
FImGuiContextProxy& ContextProxy = Entry.Value.ContextProxy;
ContextProxy.SetAsCurrent();
// Tick context proxy to end the old frame and starts a new one.
ContextProxy.Tick(DeltaSeconds);
auto& ContextData = Pair.Value;
if (ContextData.CanTick())
{
ContextData.ContextProxy.SetAsCurrent();
ContextData.ContextProxy.Tick(DeltaSeconds);
}
}
}
FImGuiContextManager::FContextData& FImGuiContextManager::FindOrAddContextData(int32 Index)
#if WITH_EDITOR
FImGuiContextManager::FContextData& FImGuiContextManager::GetEditorContextData()
{
FContextData* Data = Contexts.Find(Index);
FContextData* Data = Contexts.Find(Utilities::EDITOR_CONTEXT_INDEX);
if (!Data)
if (UNLIKELY(!Data))
{
Data = &Contexts.Add(Index);
if (!Data->ContextProxy.OnDraw().IsBoundToObject(&ImGuiDemo))
{
Data->ContextProxy.OnDraw().AddRaw(&ImGuiDemo, &FImGuiDemo::DrawControls);
}
Data = &Contexts.Emplace(Utilities::EDITOR_CONTEXT_INDEX, FContextData{ GetEditorContextName(), ImGuiDemo });
}
return *Data;
}
#endif // WITH_EDITOR
#if !WITH_EDITOR
FImGuiContextManager::FContextData& FImGuiContextManager::GetStandaloneWorldContextData()
{
FContextData* Data = Contexts.Find(Utilities::STANDALONE_GAME_CONTEXT_INDEX);
if (UNLIKELY(!Data))
{
Data = &Contexts.Emplace(Utilities::STANDALONE_GAME_CONTEXT_INDEX, FContextData{ GetWorldContextName(), ImGuiDemo });
}
return *Data;
}
#endif // !WITH_EDITOR
FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(const UWorld& World)
{
using namespace Utilities;
const FWorldContext* WorldContext = GetWorldContext(World);
const int32 Index = GetWorldContextIndex(*WorldContext);
checkf(Index != Utilities::INVALID_CONTEXT_INDEX, TEXT("Couldn't find context index for world %s: WorldType = %d"),
*World.GetName(), World.WorldType);
#if WITH_EDITOR
checkf(!GEngine->IsEditor() || Index != Utilities::EDITOR_CONTEXT_INDEX,
TEXT("Context index %d is reserved for editor and cannot be used for world %s: WorldType = %d, NetMode = %d"),
Index, *World.GetName(), World.WorldType, World.GetNetMode());
#endif
FContextData* Data = Contexts.Find(Index);
#if WITH_EDITOR
if (UNLIKELY(!Data))
{
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), ImGuiDemo, WorldContext->PIEInstance });
}
else
{
// Because we allow (for the sake of continuity) to map different PIE instances to the same index.
Data->PIEInstance = WorldContext->PIEInstance;
}
#else
if (UNLIKELY(!Data))
{
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), ImGuiDemo });
}
#endif
return *Data;
}

View File

@ -4,7 +4,6 @@
#include "ImGuiContextProxy.h"
#include "ImGuiDemo.h"
#include "Utilities/WorldContextIndex.h"
// Manages ImGui context proxies.
@ -20,17 +19,20 @@ public:
FImGuiContextManager(FImGuiContextManager&&) = delete;
FImGuiContextManager& operator=(FImGuiContextManager&&) = delete;
// Get or create default ImGui context proxy. In editor this is the editor context proxy and in standalone game
// context proxy for the only world and the same value as returned from GetWorldContextProxy.
//
// If proxy doesn't exist then it will be created and initialized.
FImGuiContextProxy& GetDefaultContextProxy() { return FindOrAddContextData(Utilities::DEFAULT_CONTEXT_INDEX).ContextProxy; }
~FImGuiContextManager();
#if WITH_EDITOR
// Get or create editor ImGui context proxy.
FORCEINLINE FImGuiContextProxy& GetEditorContextProxy() { return GetEditorContextData().ContextProxy; }
#endif
#if !WITH_EDITOR
// Get or create standalone game ImGui context proxy.
FORCEINLINE FImGuiContextProxy& GetWorldContextProxy() { return GetStandaloneWorldContextData().ContextProxy; }
#endif
// Get or create ImGui context proxy for given world.
//
// If proxy doesn't yet exist then it will be created and initialized. If proxy already exists then associated
// world data will be updated.
FImGuiContextProxy& GetWorldContextProxy(UWorld& World);
FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World) { return GetWorldContextData(World).ContextProxy; }
// Get context proxy by index, or null if context with that index doesn't exist.
FORCEINLINE FImGuiContextProxy* GetContextProxy(int32 ContextIndex)
@ -43,13 +45,49 @@ public:
private:
#if WITH_EDITOR
struct FContextData
{
TWeakObjectPtr<UWorld> World;
FContextData(const FString& ContextName, FImGuiDemo& Demo, int32 InPIEInstance = -1)
: PIEInstance(InPIEInstance)
, ContextProxy(ContextName)
{
ContextProxy.OnDraw().AddRaw(&Demo, &FImGuiDemo::DrawControls);
}
FORCEINLINE bool CanTick() const { return PIEInstance < 0 || GEngine->GetWorldContextFromPIEInstance(PIEInstance); }
int32 PIEInstance = -1;
FImGuiContextProxy ContextProxy;
};
FContextData& FindOrAddContextData(int32 Index);
#else
struct FContextData
{
FContextData(const FString& ContextName, FImGuiDemo& Demo)
: ContextProxy(ContextName)
{
ContextProxy.OnDraw().AddRaw(&Demo, &FImGuiDemo::DrawControls);
}
FORCEINLINE bool CanTick() const { return true; }
FImGuiContextProxy ContextProxy;
};
#endif // WITH_EDITOR
#if WITH_EDITOR
FContextData& GetEditorContextData();
#endif
#if !WITH_EDITOR
FContextData& GetStandaloneWorldContextData();
#endif
FContextData& GetWorldContextData(const UWorld& World);
TMap<int32, FContextData> Contexts;

View File

@ -11,7 +11,29 @@
static constexpr float DEFAULT_CANVAS_WIDTH = 3840.f;
static constexpr float DEFAULT_CANVAS_HEIGHT = 2160.f;
FImGuiContextProxy::FImGuiContextProxy()
namespace
{
FString GetSaveDirectory()
{
FString Directory = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("ImGui"));
// Make sure that directory is created.
IPlatformFile::GetPlatformPhysical().CreateDirectory(*Directory);
return Directory;
}
FString GetIniFile(const FString& Name)
{
static FString SaveDirectory = GetSaveDirectory();
return FPaths::Combine(SaveDirectory, Name + TEXT(".ini"));
}
}
FImGuiContextProxy::FImGuiContextProxy(const FString& InName)
: Name(InName)
, IniFilename(TCHAR_TO_ANSI(*GetIniFile(InName)))
{
// Create context.
Context = ImGui::CreateContext();
@ -22,6 +44,9 @@ FImGuiContextProxy::FImGuiContextProxy()
// Start initialization.
ImGuiIO& IO = ImGui::GetIO();
// Set session data storage.
IO.IniFilename = IniFilename.c_str();
// Use pre-defined canvas size.
IO.DisplaySize = { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT };
@ -53,18 +78,26 @@ FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other)
, DrawEvent(std::move(Other.DrawEvent))
, InputState(std::move(Other.InputState))
, DrawLists(std::move(Other.DrawLists))
, Name(std::move(Other.Name))
, IniFilename(std::move(Other.IniFilename))
{
Other.Context = nullptr;
}
FImGuiContextProxy& FImGuiContextProxy::operator=(FImGuiContextProxy&& Other)
{
Context = std::move(Other.Context);
Other.Context = nullptr;
// Swapping context so it can be destroyed with the other object.
using std::swap;
swap(Context, Other.Context);
// Just moving remaining data that doesn't affect cleanup.
bHasActiveItem = Other.bHasActiveItem;
DrawEvent = std::move(Other.DrawEvent);
InputState = std::move(Other.InputState);
DrawLists = std::move(Other.DrawLists);
Name = std::move(Other.Name);
IniFilename = std::move(Other.IniFilename);
return *this;
}
@ -75,14 +108,12 @@ FImGuiContextProxy::~FImGuiContextProxy()
// Set this context in ImGui for de-initialization (any de-allocations will be tracked in this context).
SetAsCurrent();
// Shutdown to save data etc.
ImGui::Shutdown();
// Destroy the context.
// Save context data and destroy.
ImGuiImplementation::SaveCurrentContextIniSettings(IniFilename.c_str());
ImGui::DestroyContext(Context);
// Set default context in ImGui to keep global context pointer valid.
ImGui::SetCurrentContext(&GetDefaultContext());
ImGui::SetCurrentContext(&ImGuiImplementation::GetDefaultContext());
}
}

View File

@ -6,6 +6,8 @@
#include <imgui.h>
#include <string>
class FImGuiInputState;
@ -15,7 +17,7 @@ class FImGuiContextProxy
{
public:
FImGuiContextProxy();
FImGuiContextProxy(const FString& Name);
~FImGuiContextProxy();
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
@ -24,6 +26,9 @@ public:
FImGuiContextProxy(FImGuiContextProxy&& Other);
FImGuiContextProxy& operator=(FImGuiContextProxy&& Other);
// Get the name of this context.
const FString& GetName() const { return Name; }
// Get draw data from the last frame.
const TArray<FImGuiDrawList>& GetDrawData() const { return DrawLists; }
@ -33,6 +38,9 @@ public:
// Set input state to be used by this context.
void SetInputState(const FImGuiInputState* SourceInputState) { InputState = SourceInputState; }
// If context is currently using input state to remove then remove that binding.
void RemoveInputState(const FImGuiInputState* InputStateToRemove) { if (InputState == InputStateToRemove) InputState = nullptr; }
// Is this context the current ImGui context.
bool IsCurrentContext() const { return ImGui::GetCurrentContext() == Context; }
@ -63,4 +71,7 @@ private:
const FImGuiInputState* InputState = nullptr;
TArray<FImGuiDrawList> DrawLists;
FString Name;
std::string IniFilename;
};

View File

@ -22,9 +22,17 @@
#endif // PLATFORM_WINDOWS
// This is exposing ImGui default context for the whole module.
// This is assuming that we don't define custom GImGui and therefore have GImDefaultContext defined in imgui.cpp.
ImGuiContext& GetDefaultContext()
namespace ImGuiImplementation
{
return GImDefaultContext;
// This is exposing ImGui default context for the whole module.
// This is assuming that we don't define custom GImGui and therefore have GImDefaultContext defined in imgui.cpp.
ImGuiContext& GetDefaultContext()
{
return GImDefaultContext;
}
void SaveCurrentContextIniSettings(const char* Filename)
{
SaveIniSettingsToDisk(Filename);
}
}

View File

@ -5,5 +5,12 @@
#include <imgui.h>
// Get default context created by ImGui framework.
ImGuiContext& GetDefaultContext();
// Gives access to selected ImGui implementation features.
namespace ImGuiImplementation
{
// Get default context created by ImGui framework.
ImGuiContext& GetDefaultContext();
// Save current context settings.
void SaveCurrentContextIniSettings(const char* Filename);
}

View File

@ -5,15 +5,13 @@
#include "ImGuiModuleManager.h"
#include "ImGuiInteroperability.h"
#include "Utilities/WorldContextIndex.h"
#include <imgui.h>
FImGuiModuleManager::FImGuiModuleManager()
{
// Make sure that default ImGui context is setup.
ContextManager.GetDefaultContextProxy();
// Typically we will use viewport created events to add widget to new game viewports.
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
@ -37,6 +35,15 @@ FImGuiModuleManager::~FImGuiModuleManager()
ViewportCreatedHandle.Reset();
}
// Remove still active widgets (important during hot-reloading).
for (auto& Widget : Widgets)
{
if (Widget.IsValid())
{
Widget.Pin()->Detach();
}
}
// Deactivate this manager.
Uninitialize();
}
@ -141,24 +148,21 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
checkf(GameViewport, TEXT("Null game viewport."));
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
const int32 ContextIndex = Utilities::GetWorldContextIndex(GameViewport);
// This makes sure that context for this world is created.
auto& Proxy = ContextManager.GetWorldContextProxy(*GameViewport->GetWorld());
// Get widget for this world.
auto& ViewportWidget = ViewportWidgets.FindOrAdd(ContextIndex);
if (!ViewportWidget.IsValid())
TSharedPtr<SImGuiWidget> Widget;
SAssignNew(Widget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport)
.ContextIndex(Utilities::GetWorldContextIndex(GameViewport));
if (TWeakPtr<SImGuiWidget>* Slot = Widgets.FindByPredicate([](auto& Widget) { return !Widget.IsValid(); }))
{
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this).ContextIndex(ContextIndex);
check(ViewportWidget.IsValid());
*Slot = Widget;
}
else
{
Widgets.Emplace(Widget);
}
// Bind widget's input to context for this world.
Proxy.SetInputState(&ViewportWidget->GetInputState());
// We should always have one viewport per context index at a time (this will be validated by widget).
ViewportWidget->AttachToViewport(GameViewport);
}
void FImGuiModuleManager::AddWidgetToAllViewports()

View File

@ -61,8 +61,8 @@ private:
// Manager for textures resources.
FTextureManager TextureManager;
// Slate widgets that we attach to game viewports.
TMap<int32, TSharedPtr<SImGuiWidget>> ViewportWidgets;
// Slate widgets that we created.
TArray<TWeakPtr<SImGuiWidget>> Widgets;
FDelegateHandle TickDelegateHandle;
FDelegateHandle ViewportCreatedHandle;

View File

@ -47,10 +47,16 @@ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SImGuiWidget::Construct(const FArguments& InArgs)
{
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
checkf(InArgs._GameViewport, TEXT("Null Game Viewport argument"));
ModuleManager = InArgs._ModuleManager;
GameViewport = InArgs._GameViewport;
ContextIndex = InArgs._ContextIndex;
// NOTE: We could allow null game viewports (for instance to attach to non-viewport widgets) but we would need
// to modify a few functions that assume valid viewport pointer.
GameViewport->AddViewportWidgetContent(SharedThis(this), IMGUI_WIDGET_Z_ORDER);
// Disable mouse cursor over this widget as we will use ImGui to draw it.
SetCursor(EMouseCursor::None);
@ -60,10 +66,11 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
// Register to get post-update notifications, so we can clean frame updates.
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
// Register self-debug function.
// Bind this widget to its context proxy.
auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
checkf(ContextProxy, TEXT("Missing context during widget construction: ContextIndex = %d"), ContextIndex);
ContextProxy->OnDraw().AddRaw(this, &SImGuiWidget::OnDebugDraw);
ContextProxy->SetInputState(&InputState);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
@ -73,27 +80,20 @@ SImGuiWidget::~SImGuiWidget()
if (auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
{
ContextProxy->OnDraw().RemoveAll(this);
ContextProxy->RemoveInputState(&InputState);
}
// Unregister from post-update notifications.
ModuleManager->OnPostImGuiUpdate().RemoveAll(this);
}
void SImGuiWidget::AttachToViewport(UGameViewportClient* InGameViewport, bool bResetInput)
void SImGuiWidget::Detach()
{
checkf(InGameViewport, TEXT("Null InGameViewport"));
checkf(!GameViewport.IsValid() || GameViewport.Get() == InGameViewport,
TEXT("Widget is attached to another game viewport and will be available for reuse only after this session ")
TEXT("ends. ContextIndex = %d, CurrentGameViewport = %s, InGameViewport = %s"),
ContextIndex, *GameViewport->GetName(), InGameViewport->GetName());
if (bResetInput)
if (GameViewport.IsValid())
{
ResetInputState();
GameViewport->RemoveViewportWidgetContent(SharedThis(this));
GameViewport.Reset();
}
GameViewport = InGameViewport;
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(SharedThis(this)), IMGUI_WIDGET_Z_ORDER);
}
void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
@ -284,13 +284,6 @@ bool SImGuiWidget::IgnoreKeyEvent(const FKeyEvent& KeyEvent) const
return false;
}
void SImGuiWidget::ResetInputState()
{
bInputEnabled = false;
SetVisibilityFromInputEnabled();
UpdateInputMode(false, false);
}
void SImGuiWidget::SetVisibilityFromInputEnabled()
{
// If we don't use input disable hit test to make this widget invisible for cursors hit detection.
@ -479,12 +472,14 @@ void SImGuiWidget::OnDebugDraw()
bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0;
if (bDebug)
{
ImGui::SetNextWindowSize(ImVec2(380, 340), ImGuiSetCond_Once);
ImGui::SetNextWindowSize(ImVec2(380, 360), ImGuiSetCond_Once);
if (ImGui::Begin("ImGui Widget Debug", &bDebug))
{
ImGui::Columns(2, nullptr, false);
TwoColumns::Value("Context Index", ContextIndex);
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
TwoColumns::Value("Context Name", ContextProxy ? *ContextProxy->GetName() : TEXT("< Null >"));
TwoColumns::Value("Game Viewport", *GameViewport->GetName());
ImGui::Separator();

View File

@ -19,6 +19,7 @@ public:
SLATE_BEGIN_ARGS(SImGuiWidget)
{}
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
SLATE_ARGUMENT(UGameViewportClient*, GameViewport)
SLATE_ARGUMENT(int32, ContextIndex)
SLATE_END_ARGS()
@ -35,12 +36,8 @@ public:
// Get the game viewport to which this widget is attached.
const TWeakObjectPtr<UGameViewportClient>& GetGameViewport() const { return GameViewport; }
// Attach this widget to a target game viewport.
// Widget can be attached to only one viewport at a time but can be reused after its last viewport becomes invalid
// at the end of a session. Widgets are weakly attached, so once destroyed they are automatically removed.
// @param InGameViewport - Target game viewport
// @param bResetInput - If true (default), input will be reset back to a default state
void AttachToViewport(UGameViewportClient* InGameViewport, bool bResetInput = true);
// Detach widget from viewport assigned during construction (effectively allowing to dispose this widget).
void Detach();
//----------------------------------------------------------------------------------------------------
// SWidget overrides
@ -92,8 +89,6 @@ private:
bool IgnoreKeyEvent(const FKeyEvent& KeyEvent) const;
void ResetInputState();
// Update visibility based on input enabled state.
void SetVisibilityFromInputEnabled();

View File

@ -0,0 +1,24 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "WorldContext.h"
namespace Utilities
{
const FWorldContext* GetWorldContextFromNetMode(ENetMode NetMode)
{
checkf(GEngine, TEXT("GEngine required to get list of worlds."));
for (const FWorldContext& WorldContext : GEngine->GetWorldContexts())
{
if (WorldContext.World() && WorldContext.World()->GetNetMode() == NetMode)
{
return &WorldContext;
}
}
return nullptr;
}
}

View File

@ -0,0 +1,41 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Core.h>
#include <Engine.h>
// Utilities helping to get a World Context.
namespace Utilities
{
template<typename T>
FORCEINLINE const FWorldContext* GetWorldContext(const TWeakObjectPtr<T>& Obj)
{
return Obj.IsValid() ? GetWorldContext(*Obj.Get()) : nullptr;
}
template<typename T>
FORCEINLINE const FWorldContext* GetWorldContext(const T* Obj)
{
return Obj ? GetWorldContext(*Obj) : nullptr;
}
FORCEINLINE const FWorldContext* GetWorldContext(const UGameInstance& GameInstance)
{
return GameInstance.GetWorldContext();
}
FORCEINLINE const FWorldContext* GetWorldContext(const UGameViewportClient& GameViewportClient)
{
return GetWorldContext(GameViewportClient.GetGameInstance());
}
FORCEINLINE const FWorldContext* GetWorldContext(const UWorld& World)
{
return GetWorldContext(World.GetGameInstance());
}
const FWorldContext* GetWorldContextFromNetMode(ENetMode NetMode);
}

View File

@ -2,8 +2,7 @@
#pragma once
#include <Core.h>
#include <Engine.h>
#include "Utilities/WorldContext.h"
// Utilities mapping worlds to indices that we use to identify ImGui contexts.
@ -12,51 +11,34 @@
namespace Utilities
{
// Default context index for non PIE worlds.
static constexpr int32 DEFAULT_CONTEXT_INDEX = 0;
// Invalid context index for parameters that cannot be resolved to a valid world.
static constexpr int32 INVALID_CONTEXT_INDEX = -1;
// Standalone context index.
static constexpr int32 STANDALONE_GAME_CONTEXT_INDEX = 0;
#if WITH_EDITOR
template<typename T>
FORCEINLINE int32 GetWorldContextIndex(const TWeakObjectPtr<T>& Obj)
{
return Obj.IsValid() ? GetWorldContextIndex(*Obj.Get()) : INVALID_CONTEXT_INDEX;
}
// Editor context index.
static constexpr int32 EDITOR_CONTEXT_INDEX = 0;
template<typename T>
FORCEINLINE int32 GetWorldContextIndex(const T* Obj)
FORCEINLINE int32 GetWorldContextIndex(const T& Obj)
{
return Obj ? GetWorldContextIndex(*Obj) : INVALID_CONTEXT_INDEX;
const FWorldContext* WorldContext = GetWorldContext(Obj);
return WorldContext ? GetWorldContextIndex(*WorldContext) : INVALID_CONTEXT_INDEX;
}
FORCEINLINE int32 GetWorldContextIndex(const FWorldContext& WorldContext)
{
// In standalone game (WorldType = Game) we have only one context with index 0 (see DEFAULT_CONTEXT_INDEX).
// In standalone game (WorldType = Game) we have only one context with index 0 (see GAME_CONTEXT_INDEX).
// In editor, we keep 0 for editor and use PIEInstance to index worlds. In simulation or standalone single-PIE
// sessions PIEInstance is 0, but since there is only one world we can change it without causing any conflicts.
// In single-PIE with dedicated server or multi-PIE sessions worlds have PIEInstance starting from 1 for server
// and 2+ for clients, what maps directly to our index.
return WorldContext.WorldType == EWorldType::PIE ? FMath::Max(WorldContext.PIEInstance, 1) : DEFAULT_CONTEXT_INDEX;
}
FORCEINLINE int32 GetWorldContextIndex(const UGameInstance& GameInstance)
{
return GetWorldContextIndex(GameInstance.GetWorldContext());
}
FORCEINLINE int32 GetWorldContextIndex(const UGameViewportClient& GameViewportClient)
{
return GetWorldContextIndex(GameViewportClient.GetGameInstance());
}
FORCEINLINE int32 GetWorldContextIndex(const UWorld& World)
{
return GetWorldContextIndex(World.GetGameInstance());
return WorldContext.WorldType == EWorldType::PIE ? FMath::Max(WorldContext.PIEInstance, 1) : STANDALONE_GAME_CONTEXT_INDEX;
}
#else
@ -65,7 +47,7 @@ namespace Utilities
constexpr int32 GetWorldContextIndex(const T&)
{
// The only option is standalone game with one context.
return DEFAULT_CONTEXT_INDEX;
return STANDALONE_GAME_CONTEXT_INDEX;
}
#endif // #if WITH_EDITOR