[Bug] FExtensibilityManager Needs Events

Hey guys - after a bunch of blood, sweat and mountain dew I’ve determined that there’s a pretty big flaw with FExtensibilityManager. It doesn’t have any events that a module could hook to force UI rebuilds when new extenders are added or removed.

I thought I was doing something wrong for hours until I determined that it’s an order of events problem because the FExtensibilityManager has no way to report - someone added a new menu extender and so the extenders are only sampled the first time when the UI is first created.

Here’s an example (make sure you write this code in a game or game loaded module):

MenuExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension(
    "FileLoadAndSave",
    EExtensionHook::After,
    NULL,
    FMenuExtensionDelegate::CreateStatic( &MenuAppeared ) );

FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor"));
LevelEditor.GetMenuExtensibilityManager()->AddExtender(MenuExtender);

FBlueprintEditorModule& BPEditor = FModuleManager::GetModuleChecked(TEXT("Kismet"));
BPEditor.GetMenuExtensibilityManager()->AddExtender(MenuExtender);

And the event…

static void MenuAppeared(FMenuBuilder& MenuBuilder)
{
    GLog->Log(TEXT("KAPLAH!"));
    MenuBuilder.AddWidget(SNew(STextBlock).Text(TEXT("Compliance")), TEXT(""));
}

What you’ll see is that for the level editor, no message is printed out when you open the File menu and no Compliance widget is added - all because the level editor UI existed at a point before my module was loaded. But - summon a blueprint editor, which has to be built from scratch and click File and you’ll see the menu item show up and the message printed out.

I know the plugin stuff is just getting off the ground - so I’ll offer up my plugin rule of thumb. If it can be extended, make sure it can report when it changes.

Hey,

So a few things. The workflow for adding extensions to the editor was made with the assumption that the editor would be restarted upon loading up such extensions. Having UIs which regenerate on demand is a bit of an edge case, but it can be done via ‘Reload’ (which is just ‘Unload’ and ‘Load’ module while ensuring nothing happens in between). Reload is currently not exposed through the Module manager (a bug, it’s being dealt with now), You can send it via exec command though “module reload XXX” where XXX is the module name, or it can be called in code with something like:

FModuleManager::Get().UnloadModule( ModuleName );
FModuleManager::Get().LoadModule( ModuleName );

Also, the Level Editor module currently crashes on reload. That’s also a bug that’s being dealt with.

Finally, it should be noted that if your extensions are not getting loaded up on startup (after a restart), you may not be hooking up your extensions early enough. They should be getting hooked up on module startup, which should be getting called in your game modules’ StartupModule function.

Awesome thanks for the tip Zane.

So I get the desire to just say reload the editor, but I don’t think what I’m expecting is an edge case. With my game module, it isn’t loaded until I compile in the editor. So unless the editor restarts after compiling a game (or editor plugin when that system exists) which I image woul be a jarring workflow, a game module will never be able to add UI to certain locations because they already exist by the time it is eventually loaded.

Also the code above is running in my game’s StartupModule, forgot to mention that.

With my game module, it isn’t loaded until I compile in the editor

That sounds like the root issue. Your game module is supposed to be directly loaded by the engine during the init phase, well before any editor UI is initialized. If your module isn’t being loaded, chances are that its named incorrectly. The engine assumes that the primary game module to load matches your project name. So if your project is called ‘MyGame’, your primary game module should be called ‘MyGame’. If you have additional game modules to load, you can do those in your StartupModule() function for MyGame. You can see the ExampleGame code for how that works.

Perhaps, will take a look at it tonight, but I think they match.

Are you concerned that iteration time would be painful for a plugin with a lot of UI, or problems with game team members pulling the latest code, loading up the game, having to recompile and then restart the editor?

We iterate on editor modules here at Epic all the time, by dynamically recompiling the module. Usually we shutdown the UI bits that would be affected before we recompile, then resummon those afterwards. Sometimes we write some helper code to do that for us, so you can recompile and apply the changes in one click. However, for changes that affect the Level Editor UI specifically, we have some more work to do to make it easier to see your changes without restarting, like Zane said.

Sounds good, I’ve never been able to reload the level editor module so I have no reference for how much time it takes between iterations, but it sounds like you guys have thought this through.

Hey Mike, I think I have the cause of what you’ve identified to be the root issue.

  1. When you guys compile in the editor, you use random numbers or whatever to create unique module dlls, ex: MyEditor-1667.dll so that you can load multiple dlls into the process as people iterate…because unloading a library is a dangerous thing.

  2. I did all my compiling with the Debug configuration in Visual Studio.

Result:

Nobody is compiling the release version “Development” version of MyEditor.dll. So every time I load in the editor, there’s never a default module to load for my game, so I have to compile it to load it in the editor, long after the LevelEditor module has loaded.

After compiling the development version in Visual Studio,

Success!

Hmm, until Debug is useful for Rocket users, it probably shouldn’t be included as a generated configuration. What do you think?

Glad you found a workaround, and thanks for the additional info.

Debug builds are supposed to be working correctly for Rocket users. The idea is that it launches the game with the “-debug” parameter, which tells the engine to load the “debug” copy of your game modules, instead of the “development” version. We’ll investigate here and see if we can reproduce this issue, and get it fixed for the next release. Stay tuned.

–Mike

OK there is a legit issue that we fixed for the next release, where clicking “Recompile” when running a “-debug” RocketEditor will cause the game modules to be compiled with Development configuration regardless. So that fix will be coming.

Can you clarify something for me though? We were talking about your build configuration issue here and want to understand the full repro case.

Does the following work OK for you?

  • Make sure no Rocket editor is currently running
  • Start with clean binary folders. (Wipe out your intermediates and compiled game binaries.)
  • Load Visual Studio, switch to Debug build configuration and compile your game module
  • Hit Ctrl+F5 to launch RocketEditor from within Visual Studio. It should automatically use the “-debug” command-line parameter, which should tell Rocket to load the debug version of your game module.
  • Your DEBUG game module should be loaded at startup. You should not need to click the “Compile” button inside the editor. (If you did click Compile, it will actually generate a “Development” game module due to the aforementioned bug that we fixed for the upcoming release.)

Also, we want to make sure that you aren’t trying to compile from Visual Studio while the Rocket editor is already running. If that is a workflow that you are expecting to be supported, please let us know and we’ll ponder about it.

Thanks!

–Mike

Hey Mike,
I think several of my problems came from my initial workflow. Instead of starting rocket through VS, I was launching rocket through explorer. So I had lots of expectations like, debug option in settings to ensure debug dlls would be loaded.

I’ll double check tonight that Debug does work and report back, but i would consider this bug mostly a user error, since I wasn’t following the expected/documented workflow.

We don’t look at it as “user error”, we look at it as we need to do a better job advertising the expected workflow, or improving the workflow so the “most intuitive thing” actually works. :slight_smile: