BlueprintCompilationManager ignores Custom Compilers for Blueprints

Hey there, since 4.17 and the introduction of the BlueprintCompilationManager, Custom compilers for blueprints no longer work.

Normally, one could create a blueprint compiler and register it like this.

// Register the compiler for the ZD AnimBP
	IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>("KismetCompiler");
	KismetCompilerModule.GetCompilers().Add(this);

IKismetCompilerInterface having the interface calls for CanCompile, PreCompile, PostCompile and Compile among others.

This is good design, and lets users create custom compilers as I created with the PaperZD blueprint compiler.

The problem is that with BlueprintCompilationManager, this is replaced with a more… forced (or straight hardcoded) solution.

The struct FCompilerData holds the compiler that will be used but it uses this method to get which compiler context will be used (not IKismetCompilerInterface)

TSharedPtr<FKismetCompilerContext> FKismetCompilerContext::GetCompilerForBP(UBlueprint* BP, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions)
{
	if(UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP))
	{
		return TSharedPtr<FKismetCompilerContext>(new FAnimBlueprintCompiler(AnimBP, InMessageLog, InCompileOptions, nullptr));
	}
	else
	{
		return TSharedPtr<FKismetCompilerContext>(new FKismetCompilerContext(BP, InMessageLog, InCompileOptions, nullptr));
	}
}

As you can see, this makes it totally imposible to use the extended compiler context, making the “Compilers” array useless unless the CompilationManager is turned off.

Now, i understand that this is working deep on the engine, but the solution presented makes it totally imposible to use custom blueprints that can be compiled differently…
Is there something that can be done with this, patches can be created to bypass this or use the BlueprintPreCompile/PostCompile events to try to prepare the compilation and run the CustomKismetCompiler so as to “simulate” old behaviour, but the old solution was better in terms of flexibility

EDIT:
If this could be disabled “per” blueprint type, so as to support BlueprintCompilationManager on some blueprints but not on those who require full compile or custom compiling, the solution would be great and complete. Changing the code from Kismet2.cpp from

void FKismetEditorUtilities::CompileBlueprint(UBlueprint* BlueprintObj, EBlueprintCompileOptions CompileFlags, FCompilerResultsLog* pResults)
{
	if(GBlueprintUseCompilationManager)
	{
		FBlueprintCompilationManager::CompileSynchronously(FBPCompileRequest(BlueprintObj, CompileFlags, pResults));
		return;
	}

....

To something like this:

void FKismetEditorUtilities::CompileBlueprint(UBlueprint* BlueprintObj, EBlueprintCompileOptions CompileFlags, FCompilerResultsLog* pResults)
{
	if(GBlueprintUseCompilationManager && BlueprintObj->SupportsCompilationManager())
	{
		FBlueprintCompilationManager::CompileSynchronously(FBPCompileRequest(BlueprintObj, CompileFlags, pResults));
		return;
	}

....

Making the function call BlueprintObj->SupportsCompilationManager(), something that base classes can opt to override and make it return false if they need Full Compile for plugins, etc

Hey HeavyBullets-

How are you creating your custom compiler for your blueprints? Where are you adding the first two lines of code at the top of your post (what class)? If possible, could you provide setup steps for me to create a local copy or a small sample project demonstrating the issue you’re having?

Hi there, first i create a child of FKismetCompilerContext, this class has the method for Compiling the custom blueprint, in which i inject custom generated functions for Transition Nodes, and Anim Notifies (for PaperZD).

My PaperZDEditorModule inherits from IModuleInterface and from IBlueprintCompiler, overriding the methods :

//IBlueprintCompiler Interface
	virtual void PreCompile(UBlueprint* Blueprint) override;
	virtual bool CanCompile(const UBlueprint* Blueprint) override;
	virtual void Compile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions, FCompilerResultsLog& Results, TArray<UObject *>* ObjLoaded) override;
	virtual void PostCompile(UBlueprint* Blueprint) override;
	//End of IBlueprintCompiler Interface

CanCompile will check if the UBlueprint is a child of PaperZDAnimBlueprint, Compile will just create the CompilerContext and call it like this:

void FPaperZDEditorModule::Compile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions, FCompilerResultsLog& Results, TArray<UObject *>* ObjLoaded)
{
	UPaperZDAnimBP *AnimBP = Cast<UPaperZDAnimBP>(Blueprint);
	check(AnimBP);

	FPaperZDCompilerContext Compiler(Blueprint, Results, CompileOptions, ObjLoaded);
	Compiler.CompileAnimBP(); //Method changed from Compile to CompileAnimBP as of 4.16

	//Mark as dirty, because if the transition function names do change, on reload they will not have been saved, but the new function will have been recompiled, giving error and forcing a manual recompile
	AnimBP->MarkPackageDirty();
}

The method CompileAnimBP was originally an override from Compile(), but it was made private… now the method CompileAnimBP is just a Compile with some extra method injection.

The Compiler context also creates an state machine and copies to the Default Object on

void FPaperZDCompilerContext::CopyTermDefaultsToDefaultObject(UObject* DefaultObject);

The problem is that my IBlueprintCompilerContext never gets called.

This is as i stated, because the Compilation Manager does some steps, but not all… and never even uses the “Compilers” array…

If you need the project, Epic has it right now for 4.15 and 4.16 and is on code review. 4.17 hasn’t been uploaded but i can handle it to you, but i need a private mail so as not to risk the source code from leaking

Hi! I’ve been wondering the same, this broke my plugin as well. You can clearly see that if Blueprint Compilation Manager is not used, the KismetCompiler module is loaded (Kismet2.cpp at line 802), and custom compilers just work. Why would they hardcode FKismetCompilerContext inside the compilation manager?

Even the bundled ScriptPlugin doesn’t expect this. In ScriptFactory.cpp, the call to FKismetEditorUtilities::CreateBlueprint uses the custom ScriptBlueprintCompiler just fine. However, Lua source code is only set after that call, and an additional call to FKismetEditorUtilities::CompileBlueprint is made to actually compile the Lua code. That used to work before, but not anymore it seems, because the custom ScriptBlueprintCompiler is bypassed.

yes… this is clearly a mistake on coding that part… we have to wait to see what’s Epic stance on the subject, but the solution clearly points to respect the Compiler Contexts, so as not to break every plugin available atm

The compilation manager fixes a number of performance and correctness problems with the old compilation model. It will be the main driver for all FKismetCompilerContext calls going forward. Compiling multiple blueprints at once is orders of magnitude faster than compiling blueprints individually, and making sure that all reflection data is up to date before generating bytecode or propagating default values is very important. The compilation manager does performs both of these functions.

In the 4.19 time frame we will make it easier to associated custom FKismetCompilerContexts with custom UBlueprints. Until then feel free to modify FKismetCompilerContext::GetCompilerForBP.

Apologies for not honoring the IBlueprintCompiler::CanCompile virtual. That model assumed that all blueprint compilers were loaded and was therefore unreliable. There were also code paths that skipped the IBlueprintCompiler interface.

The compilation manager does feel faster in some regards, but not honoring FKismetCompilerContext array and hardcoding the compiler to use on a Getter, when it was available on the interface has broken a lot of plugins.

For the moment the solution is to simply disable the compilation manager on projects that need the plugin, denying our users that new feature project wise, or simply making it seems that is our fault because the plugin will simply not work, unless they check the version notes…

Wouldn’t it be an even better solution to create a method for each type of UBlueprint class to check if it supports the CompilationManager, at least until 4.19 (which is a long way till it’s developed), that way you can use thhe Compilation manager on blueprints that support this new method, (that is only normal blueprint or anim blueprints due to the hardcode), or using the old method ONLY for those who do not support it yet. This solution would just be a simple virtual boolean Getter that we could override if we couldn’t support the new method. ( my suggestion above)

Modifying GetCompilerForBP is a non solution for us, mainly because is not our poject which has problems, but our customer’s… and to ask them to recompile the engine when most of the users have no experience with C++, or even recompiling the engine is really complicated.

Thanks for taking the time to this

Wouldn’t it be an even better solution to create a method for each type of UBlueprint class to check if it supports the CompilationManager, at least until 4.19

If the compilation manager does not meet their requirements they should disable it via the editor project setting. An editor state where the manager is half disabled is not desirable from a test coverage perspective.

Yes, but that’s project wise… what happens with people that are using the compilation manager and then, because they download a plugin (which fails), have to disable it, increasing compile times… reflecting on bad reviews on our plugins because of something that we have no control over.

There has to be a finner method of selection, project wise is not enough… at least let us flag our blueprint types as “unsupported” so they are only compiled with the old method, the presented solution would just take the creation of a simple virtual method with no overhead whatsoever

Again, partially disabling the compilation manager is not supported or well tested. It is too high risk to add such a feature to 4.18. 4.19 will support custom compilers in a more extensible manner. Until then, fully disabling the compilation manager is the only safe option.

Ok, thanks for the help… cheers!