Skip to content

Instantly share code, notes, and snippets.

@rtm223
Last active July 20, 2023 19:22
Show Gist options
  • Save rtm223/f9b6416fd203ed25d810e600e2c854bc to your computer and use it in GitHub Desktop.
Save rtm223/f9b6416fd203ed25d810e600e2c854bc to your computer and use it in GitHub Desktop.
UFUNCTION(meta=(MustImplementInBlueprint))
#include "RTMBlueprintCompiler_MustImplementFunction.h"
#define LOCTEXT_NAMESPACE "RTMValidator_MustImplementFunction"
bool URTMBlueprintCompiler_MustImplementFunction::CanValidateBlueprint(const UBlueprint* blueprint) const
{
switch(blueprint->BlueprintType)
{
case BPTYPE_Normal:
case BPTYPE_Const:
case BPTYPE_LevelScript:
return true;
case BPTYPE_MacroLibrary:
case BPTYPE_Interface:
case BPTYPE_FunctionLibrary:
return false;
default:
checkNoEntry();
static_assert(BPTYPE_MAX == 6, "BlueprintType enum has changed size, please update switch statement to comply");
return false;
}
}
void URTMBlueprintCompiler_MustImplementFunction::ProcessBlueprintCompiled(const FKismetCompilerContext& compilationContext, const FBlueprintCompiledData& data)
{
Super::ProcessBlueprintCompiled(compilationContext, data);
const UBlueprint* blueprint = compilationContext.Blueprint;
if(!CanValidateBlueprint(blueprint))
return;
TArray<const UFunction*, TInlineAllocator<16>> foundBpFunctions;
for(TFieldIterator<const UFunction> functionItr(blueprint->SkeletonGeneratedClass, EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::IncludeInterfaces); functionItr; ++functionItr)
{
if(*functionItr && functionItr->HasMetaData(MUST_IMPLEMENT_META_NAME))
{
if(!functionItr->IsNative())
{
// assume here that the iterator is going to find functions in reverse class inheritance order (children first -> BP first)
// This does seem to be the case. If this proves false, will need to build two lists and then compare after a full iteration
foundBpFunctions.Add(*functionItr);
}
else if(!foundBpFunctions.FindByPredicate([nativeFunc=*functionItr](const UFunction* function) { return function->GetFName() == nativeFunc->GetFName(); }))
{
if(ensureAlwaysMsgf(!functionItr->GetOuterUClass()->IsChildOf(UInterface::StaticClass()), TEXT("'%s' meta is not supported on Interface functions (found on I%s::%s()"), *MUST_IMPLEMENT_META_NAME.ToString(), *functionItr->GetOuterUClass()->GetName(), *functionItr->GetName()))
{
// Name comparison should be OK for UFUNCTIONs? Name clashes aren't allowed, except with Interfaces and we can't validate them
// If not then it will be necessary to check each BP function by walking up functionItr->GetSuperFunction() to test we reach the same native UFunction
const FText fmt = LOCTEXT("MustImplement", "Function '{0}' must be implemented in Blueprint");
const FText error = FText::Format(fmt, functionItr->GetDisplayNameText());
compilationContext.MessageLog.Error(*error.ToString());
}
}
}
}
}
#undef LOCTEXT_NAMESPACE
// Copyright (c) Richard Meredith AB. All Rights Reserved
#pragma once
#include "CoreMinimal.h"
#include "BlueprintCompilerExtension.h"
#include "RTMBlueprintCompiler_MustImplementFunction.generated.h"
UCLASS(NotBlueprintable, NotBlueprintType, MinimalAPI)
class URTMBlueprintCompiler_MustImplementFunction : public UBlueprintCompilerExtension
{
GENERATED_BODY()
protected:
bool CanValidateBlueprint(const UBlueprint* blueprint) const;
virtual void ProcessBlueprintCompiled(const FKismetCompilerContext& compilationContext, const FBlueprintCompiledData& data) override;
private:
const FName MUST_IMPLEMENT_META_NAME = TEXT("MustImplementInBlueprint");
};
@rtm223
Copy link
Author

rtm223 commented Aug 8, 2022

Adds a UFUNCTION meta that will enforce child Blueprints need to implement the specified function. Blueprint will not compile until the implementation is present

Usage:

  • Add the above classes to your editor module
  • In your module StartupModule(), add the following line to register the compiler extension
    FBlueprintCompilationManager::RegisterCompilerExtension(UBlueprint::StaticClass(), NewObject<URTMBlueprintCompiler_MustImplementFunction>());
  • Add the meta to UFUNCTIONS:
UFUNCTION(BlueprintImplementableEvent, meta=(MustImplementInBlueprint))
void FunctionMustBeImplemented() const;

Error Reporting:

image

Notes:

  • Tested in UE4.27 & UE5.0
  • Given Blueprint A that implements the function, allows all children of A to use that implementation without issue
  • Does not work for Interface functions - interfaces create stub functions automatically, so detecting the existence of a function is not a suitable check

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