// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Misc/Build.h" #if WITH_AUTOMATION_WORKER #include "Containers/Array.h" #include "UObject/UObjectBaseUtility.h" #include "UObject/UObjectGlobals.h" #include "UObject/Package.h" #include "Components/SpawnHelper.h" DEFINE_LOG_CATEGORY_STATIC(LogObjectBuilder, Log, Log); template class TObjectBuilder final { public: TObjectBuilder(UObject* InOwner = static_cast(GetTransientPackage())) : Object(NewObject(InOwner)) { static_assert(std::is_base_of::value, "Template param must be a UObject type"); static_assert(!std::is_base_of::value, "Template param must not be an Actor type, supply a FSpawnHelper to the constructor for Actor configuration"); check(Object); } TObjectBuilder(FSpawnHelper& Spawner, UClass* Clazz = nullptr) : Object(nullptr) { static_assert(std::is_base_of::value, "Template param must be an Actor type, use the default constructor for non-actor UObjects"); FActorSpawnParameters DeferredConstructionParams; DeferredConstructionParams.bDeferConstruction = true; Object = &Spawner.SpawnActor(DeferredConstructionParams, Clazz); } template TObjectBuilder& SetParam(const FName& InPropertyName, T InValue) { using Raw = std::decay_t; if (!Object) { UE_LOG(LogObjectBuilder, Error, TEXT("Tried to SetParam on property %s after Spawning. ObjectBuilder can only be used to paramaterise pre-spawn"), *InPropertyName.ToString()); return *this; } auto TrySet = [&](auto Property) { if (!Property) { UE_LOG(LogObjectBuilder, Error, TEXT("Failed to find %s property %s on object %s, check type and Property name are correct"), *GetTypeName(), *InPropertyName.ToString(), *Object->GetName()); return; } if (!IsCompatibleWith(*Property)) { UE_LOG(LogObjectBuilder, Error, TEXT("Type mismatch: Tried to set %s property %s on object %s to a %s"), *Property->GetCPPType(nullptr, 0), *InPropertyName.ToString(), *Object->GetName(), *GetTypeName()); return; } if (auto TypeMatched = Property->template ContainerPtrToValuePtr(Object)) { *TypeMatched = InValue; } else { UE_LOG(LogObjectBuilder, Error, TEXT("Container type mismatch: Could not cast from %s to %s"), *Property->GetCPPType(nullptr, 0), *GetTypeName()); } }; if constexpr (TIsArray::Value) { TrySet(FindFProperty(Object->GetClass(), InPropertyName)); } else if constexpr (TIsTMap::Value) { TrySet(FindFProperty(Object->GetClass(), InPropertyName)); } else if constexpr (TIsTSet::Value) { TrySet(FindFProperty(Object->GetClass(), InPropertyName)); } else { if (auto Property = FindFProperty(Object->GetClass(), InPropertyName)) { if (this->template IsCompatibleWith(*Property)) { Property->SetValue_InContainer(Object, &InValue); } else { UE_LOG(LogObjectBuilder, Error, TEXT("Type mismatch: Tried to set %s property %s on object %s to a %s"), *Property->GetCPPType(nullptr, 0), *InPropertyName.ToString(), *Object->GetName(), *this->template GetTypeName()); } } else { UE_LOG(LogObjectBuilder, Error, TEXT("Failed to find %s property %s on object %s, check type and Property name are correct"), *GetTypeName(), *InPropertyName.ToString(), *Object->GetName()); } } return *this; } template T& Spawn(FTransform InTransform = FTransform::Identity) { if (!Object) { UE_LOG(LogObjectBuilder, Error, TEXT("Tried to spawn from builder multiple times. Builder can only spawn a single object")); auto* DefaultObject = NewObject(); return *DefaultObject; } if constexpr (std::is_base_of_v) { Object->FinishSpawning(InTransform); } auto& ObjectRef = *Object; Object = nullptr; return ObjectRef; } template TObjectBuilder& AddComponentTo(TComponent* InComponentToAdd = nullptr) { static_assert(std::is_base_of_v, "Can only add components to AActors"); static_assert(std::is_base_of::value, "Template param must be a UActorComponent type"); if (!Object) { UE_LOG(LogObjectBuilder, Error, TEXT("Tried to AddComponentTo actor after Spawning. ObjectBuilder can only be used to paramaterise pre-spawn")); return *this; } TComponent* Component = InComponentToAdd; if (Component) { Component->Rename(nullptr, Object); } else { Component = NewObject(Object); check(Component); } Component->RegisterComponent(); return *this; } template TObjectBuilder& AddChildActorComponentTo() { static_assert(std::is_base_of_v, "Can only add components to AActors"); static_assert(std::is_base_of::value, "Template param must be an Actor type"); if (!Object) { UE_LOG(LogObjectBuilder, Error, TEXT("Tried to AddChildActorComponentTo actor after Spawning. ObjectBuilder can only be used to paramaterise pre-spawn")); return *this; } auto* Component = NewObject(Object); Component->SetChildActorClass(TChildActorType::StaticClass()); Component->RegisterComponent(); return *this; } private: TUObject* Object; template bool IsCompatibleWith(const FProperty& Prop) const { using Raw = std::decay_t; if constexpr (std::is_pointer_v && std::is_base_of_v>) { if (auto AsProperty = CastField(&Prop)) { return std::remove_pointer_t::StaticClass()->IsChildOf(AsProperty->PropertyClass); } } else if constexpr (TIsTObjectPtr::Value) { if (auto AsProperty = CastField(&Prop)) { return Raw::ElementType::StaticClass()->IsChildOf(AsProperty->PropertyClass); } } else if constexpr (TIsTArray::Value) { if (auto AsProperty = CastField(&Prop)) { return IsCompatibleWith(*AsProperty->Inner); } } else if constexpr (TIsTSet::Value) { if (auto AsProperty = CastField(&Prop)) { return IsCompatibleWith(*AsProperty->ElementProp); } } else if constexpr (TIsTMap::Value) { if (auto AsProperty = CastField(&Prop)) { using Key = typename Raw::KeyInitType; using Value = typename Raw::ValueInitType; return IsCompatibleWith(*AsProperty->KeyProp) && IsCompatibleWith(*AsProperty->ValueProp); } } else if constexpr (TIsEnum::Value) { if (auto AsProperty = CastField(&Prop)) { return AsProperty->GetUnderlyingProperty()->GetSize() == sizeof(T); } } else if constexpr (std::is_base_of_v) { if (auto AsProperty = CastField(&Prop)) { return Raw::StaticClass()->IsChildOf(AsProperty->PropertyClass); } } else if constexpr (std::is_same_v) { if (auto AsProperty = CastField(&Prop)) { return AsProperty->Struct->GetFName() == NAME_Vector; } } else if constexpr (std::is_same_v::Value>) { if (auto AsProperty = CastField(&Prop)) { return AsProperty->Struct->GetName() == Raw::StaticStruct()->GetName(); } } else { using PropType = typename TPropType::Value; if (auto AsProperty = CastField(&Prop)) { return PropType::StaticClass()->IsChildOf(AsProperty->GetClass()); } } return false; } template FString GetTypeName() const { using Raw = std::decay_t; if constexpr (std::is_pointer_v && std::is_base_of_v>) { return FString::Printf(TEXT("%s*"), *std::remove_pointer_t::StaticClass()->GetName()); } else if constexpr (TIsTObjectPtr::Value) { using PtrType = typename Raw::ElementType; return FString::Printf(TEXT("TObjectPtr<%s>"), *GetTypeName()); } else if constexpr (TIsTArray::Value) { using ElementType = typename Raw::ElementType; return FString::Printf(TEXT("TArray<%s>"), *GetTypeName()); } else if constexpr (TIsTSet::Value) { using ElementType = typename Raw::ElementType; return FString::Printf(TEXT("TSet<%s>"), *GetTypeName()); } else if constexpr (TIsTMap::Value) { using Key = typename Raw::KeyInitType; using Value = typename Raw::ValueInitType; return FString::Printf(TEXT("TMap<%s, %s>"), *GetTypeName(), *GetTypeName()); } else if constexpr (TIsEnum::Value) { return TEXT("EnumProperty"); } else if constexpr (std::is_same_v) { return TEXT("FVector"); } else if constexpr (std::is_base_of_v) { return FString::Printf(TEXT("%s"), *Raw::StaticClass()->GetName()); } else if constexpr (std::is_same_v::Value>) { return Raw::StaticStruct()->GetName(); } else { return TNameOf::GetName(); } } template struct TPropType { using Value = FStructProperty; }; template <> struct TPropType { using Value = FInt8Property; }; template <> struct TPropType { using Value = FByteProperty; }; template <> struct TPropType { using Value = FInt16Property; }; template <> struct TPropType { using Value = FUInt16Property; }; template <> struct TPropType { using Value = FIntProperty; }; template <> struct TPropType { using Value = FUInt32Property; }; template <> struct TPropType { using Value = FInt64Property; }; template <> struct TPropType { using Value = FUInt64Property; }; template <> struct TPropType { using Value = FBoolProperty; }; template <> struct TPropType { using Value = FFloatProperty; }; template <> struct TPropType { using Value = FDoubleProperty; }; template <> struct TPropType { using Value = FNameProperty; }; }; #endif // WITH_AUTOMATION_WORKER