Blackboard Key Filters in Non-Editor Build Configurations

Hello!

We’re using FBlackboardKeySelector a lot in our custom Utility AI system, to provide key filtering when editing assets (similar to how they’re used for behavior tree nodes). The problem I’ve noticed while profiling is that it instantiates a LOT of UObjects when our Utility AI asset gets initialized, which causes framerate spikes when any AI archetype appears for the first time.

I thought of a quick solution: since the filtering doesn’t seem all that useful in non-editor build configurations, I simply made the factory functions empty stubs for those, and it drastically improved the performance (see the code snippet below). My colleagues have suggested to post this here, first to see if there’s a reason why I shouldn’t do this, and if it might be interesting to have this optimization officially as part of the engine.

Thanks!

#if WITH_EDITOR
	AIMODULE_API void AddObjectFilter(UObject* Owner, FName PropertyName, TSubclassOf<UObject> AllowedClass);
	AIMODULE_API void AddClassFilter(UObject* Owner, FName PropertyName, TSubclassOf<UObject> AllowedClass);
	AIMODULE_API void AddEnumFilter(UObject* Owner, FName PropertyName, UEnum* AllowedEnum);
	AIMODULE_API void AddNativeEnumFilter(UObject* Owner, FName PropertyName, const FString& AllowedEnumName);
	AIMODULE_API void AddIntFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddFloatFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddBoolFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddVectorFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddRotatorFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddStringFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddNameFilter(UObject* Owner, FName PropertyName);
	AIMODULE_API void AddStructFilter(UObject* Owner, FName PropertyName, const UScriptStruct* AllowedStruct);

	template<typename T>
	void AddStructFilter(UObject* Owner, FName PropertyName)
	{
		AddStructFilter(Owner, PropertyName, TBaseStructure<T>::Get());
	}
#else
	AIMODULE_API void AddObjectFilter(UObject* Owner, FName PropertyName, TSubclassOf<UObject> AllowedClass) {}
	AIMODULE_API void AddClassFilter(UObject* Owner, FName PropertyName, TSubclassOf<UObject> AllowedClass) {}
	AIMODULE_API void AddEnumFilter(UObject* Owner, FName PropertyName, UEnum* AllowedEnum) {}
	AIMODULE_API void AddNativeEnumFilter(UObject* Owner, FName PropertyName, const FString& AllowedEnumName) {}
	AIMODULE_API void AddIntFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddFloatFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddBoolFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddVectorFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddRotatorFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddStringFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddNameFilter(UObject* Owner, FName PropertyName) {}
	AIMODULE_API void AddStructFilter(UObject* Owner, FName PropertyName, const UScriptStruct* AllowedStruct) {}

	template<typename T>
	void AddStructFilter(UObject* Owner, FName PropertyName) {}
#endif //WITH_EDITOR

Speaking with some others on the team, we all believe this is a valid optimization. The main use was to prevent users from selecting keys that don’t match the desired type. We also make use of them at initialization and before saving, but once cooked, they should not be required.

All of that being said, we are going to look to push this into the engine ourselves. Thank you for finding this and letting us know about it! We do not do much development for BT beyond bug fixes, but this looks like a good optimization I am sure many other would also like to have.

-James