├── .gitignore ├── BlueprintComponentReferencePlugin.uplugin ├── BuildPlugin.bat.template ├── Config └── FilterPlugin.ini ├── Content ├── BCRChild.uasset ├── BCRTestBlueprint.uasset ├── BCRTestBlueprintChild.uasset ├── BCR_SampleDA.uasset ├── BCR_SampleDA_Default.uasset └── BP_SampleDA.uasset ├── Images ├── BCR-Hello.png ├── BCR-Large.png ├── BCR-Nodes.png ├── BCR-Quick.png └── BCR-Variable.png ├── LICENSE ├── README.md └── Source ├── BlueprintComponentReference ├── BlueprintComponentReference.Build.cs ├── BlueprintComponentReference.cpp ├── BlueprintComponentReference.h ├── BlueprintComponentReferenceLibrary.cpp ├── BlueprintComponentReferenceLibrary.h └── CachedBlueprintComponentReference.h ├── BlueprintComponentReferenceEditor ├── BlueprintComponentReferenceCustomization.cpp ├── BlueprintComponentReferenceCustomization.h ├── BlueprintComponentReferenceEditor.Build.cs ├── BlueprintComponentReferenceEditor.cpp ├── BlueprintComponentReferenceEditor.h ├── BlueprintComponentReferenceHelper.cpp ├── BlueprintComponentReferenceHelper.h ├── BlueprintComponentReferenceMetadata.cpp ├── BlueprintComponentReferenceMetadata.h ├── BlueprintComponentReferenceVarCustomization.cpp ├── BlueprintComponentReferenceVarCustomization.h ├── K2Node_FindComponentInMap.cpp └── K2Node_FindComponentInMap.h └── BlueprintComponentReferenceTests ├── BCRTestActor.cpp ├── BCRTestActor.h ├── BCRTestActorComponent.cpp ├── BCRTestActorComponent.h ├── BCRTestDataAsset.cpp ├── BCRTestDataAsset.h ├── BCRTestStruct.cpp ├── BCRTestStruct.h ├── BlueprintComponentReferenceTests.Build.cs └── BlueprintComponentReferenceTests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /BlueprintComponentReferencePlugin.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Blueprint Component Reference", 6 | "Description": "Provides component picker for Blueprint details.", 7 | "Category": "Core", 8 | "CreatedBy": "Aquanox", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/cd7b9bbbabd242578205618e7146eecd", 12 | "SupportURL": "", 13 | "EngineVersion": "", 14 | "CanContainContent": true, 15 | "Modules": [ 16 | { 17 | "Name": "BlueprintComponentReference", 18 | "Type": "Runtime", 19 | "LoadingPhase": "PostConfigInit" 20 | }, 21 | { 22 | "Name": "BlueprintComponentReferenceTests", 23 | "Type": "UncookedOnly", 24 | "LoadingPhase": "Default" 25 | }, 26 | { 27 | "Name": "BlueprintComponentReferenceEditor", 28 | "Type": "UncookedOnly", 29 | "LoadingPhase": "Default" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /BuildPlugin.bat.template: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set PLUGIN_PATH="%cd%\BlueprintComponentReferencePlugin.uplugin" 4 | set RUNUAT_PATH="%EPIC_LIBRARY%\UE_%VERSION%\Engine\Build\BatchFiles\RunUAT.bat" 5 | set PACKAGE_PATH="%WORK_TEMP%\BCR_%VERSION%" 6 | 7 | set EXTRA_PARAMS= -StrictIncludes 8 | 9 | echo. 10 | echo Compiling for %VERSION% 11 | echo. 12 | 13 | %RUNUAT_PATH% BuildPlugin -Plugin=%PLUGIN_PATH% -Package=%PACKAGE_PATH% -HostPlatforms=Win64 -TargetPlatforms=Win64 %EXTRA_PARAMS% 14 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | 10 | /README.md 11 | -------------------------------------------------------------------------------- /Content/BCRChild.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Content/BCRChild.uasset -------------------------------------------------------------------------------- /Content/BCRTestBlueprint.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Content/BCRTestBlueprint.uasset -------------------------------------------------------------------------------- /Content/BCRTestBlueprintChild.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Content/BCRTestBlueprintChild.uasset -------------------------------------------------------------------------------- /Content/BCR_SampleDA.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Content/BCR_SampleDA.uasset -------------------------------------------------------------------------------- /Content/BCR_SampleDA_Default.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Content/BCR_SampleDA_Default.uasset -------------------------------------------------------------------------------- /Content/BP_SampleDA.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Content/BP_SampleDA.uasset -------------------------------------------------------------------------------- /Images/BCR-Hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Images/BCR-Hello.png -------------------------------------------------------------------------------- /Images/BCR-Large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Images/BCR-Large.png -------------------------------------------------------------------------------- /Images/BCR-Nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Images/BCR-Nodes.png -------------------------------------------------------------------------------- /Images/BCR-Quick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Images/BCR-Quick.png -------------------------------------------------------------------------------- /Images/BCR-Variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquanox/BlueprintComponentReferencePlugin/7811e28a9c5eba63796f58f356e6c43a563cadba/Images/BCR-Variable.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Valentin Plyasinov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub release](https://img.shields.io/github/release/aquanox/BlueprintComponentReferencePlugin.svg)](https://github.com/aquanox/BlueprintComponentReferencePlugin/releases) 2 | [![GitHub license](https://img.shields.io/github/license/aquanox/BlueprintComponentReferencePlugin)](https://github.com/aquanox/BlueprintComponentReferencePlugin/blob/main/LICENSE) 3 | [![GitHub forks](https://img.shields.io/github/forks/aquanox/BlueprintComponentReferencePlugin)](https://github.com/aquanox/BlueprintComponentReferencePlugin/network) 4 | [![GitHub stars](https://img.shields.io/github/stars/aquanox/BlueprintComponentReferencePlugin)](https://github.com/aquanox/BlueprintComponentReferencePlugin/stargazers) 5 | ![UE5](https://img.shields.io/badge/UE5-5.0+-lightgrey) 6 | 7 | ## Blueprint Component Reference Plugin for Unreal Engine 8 | 9 | Blueprint Component Reference Plugin provides a struct and set of accessors that allow referencing actor components from blueprint editor details view with a component picker, it automatically identifies context and builds list of components for selection menu. 10 | 11 | ![](Images/BCR-Quick.png) 12 | 13 | ## Features 14 | 15 | Plugin provides component picker in following use cases: 16 | * Actor class member UPROPERTY 17 | * Struct member UPROPERTY within Actor class or blueprint. 18 | * Local variable in an Actor Blueprint. 19 | * Function local variable in an Actor Blueprint. 20 | * Generic member UPROPERTY (if ActorClass meta specifier is used). 21 | * Generic Blueprint variable (if ActorClass meta specifier is used). 22 | * Array in one of above 23 | * Set in one of above 24 | * Map (Key or value) in one of above 25 | 26 | ## Component Selection Customizations 27 | 28 | Component reference property display can be customized with UPROPERTY meta specifiers. 29 | 30 | Component Filtering: 31 | * `AllowedClasses` - List of classes for component filter 32 | * `DisallowedClasses` - List of classes for component filter 33 | * `ShowNative` - Include native (created with CreateDefaultSubobject) components in picker. Default = True. 34 | * `ShowBlueprint` - Include blueprint (added in Blueprint Editor) components in picker. Default = True. 35 | * `ShowInstanced` - Include instanced components with assosicated property in picker. Default = False. 36 | * `ShowHidden` - Include any components without associated property in picker. Default = False. 37 | * `ShowEditor` - Include editor-only components in picker. Default = True. 38 | 39 | Item Display: 40 | * `NoClear` - Hide 'Clear' button. Default = False. 41 | * `NoNavigate` - Hide 'Navigate to' button. Default = False. 42 | * `NoPicker` - Disable component picker. Default = False. 43 | 44 | Out-of-Actor Use: 45 | * `ActorClass` - Class to use for component selection dropdown if context detection is not possible. 46 | 47 | Blueprint variables supported via variable editor extension. 48 | 49 | ![](Images/BCR-Variable.png) 50 | 51 | 52 | ## Unreal Engine Versions 53 | 54 | Plugin is compatible with 5.0+, ue5-main and can be modified to work with other engine versions. 55 | 56 | ## License 57 | 58 | Blueprint Component Reference Plugin is available under the MIT license. See the LICENSE file for more info. 59 | 60 | ## Examples 61 | 62 | ```c++ 63 | 64 | UCLASS() 65 | class ABCRTestActor : public AInfo 66 | { 67 | GENERATED_BODY() 68 | public: 69 | /* Simplest reference to any component within current class */ 70 | UPROPERTY(EditAnywhere, BlueprintReadOnly) 71 | FBlueprintComponentReference SimpleReference; 72 | 73 | /* Simplest reference to any native component within current class */ 74 | UPROPERTY(EditAnywhere, BlueprintReadOnly, meta=(ShowNative=true, ShowBlueprint=false)) 75 | FBlueprintComponentReference NativeOnlyReference; 76 | 77 | /* Simplest reference to any component within current class */ 78 | UPROPERTY(EditAnywhere, BlueprintReadOnly, meta=(AllowedClasses="/Script/Engine.SceneComponent")) 79 | FBlueprintComponentReference SimpleSceneCompReference; 80 | }; 81 | 82 | UCLASS() 83 | class UBCRTestDataAsset : public UDataAsset 84 | { 85 | GENERATED_BODY() 86 | public: 87 | /* Make a reference to any component within a BCRTestActor class */ 88 | UPROPERTY(EditAnywhere, meta=(ActorClass="/Script/BlueprintComponentReferenceTests.BCRTestActor")) 89 | FBlueprintComponentReference ExternalRef; 90 | }; 91 | 92 | void AMyActor::Foo() 93 | { 94 | if (auto* Component = Reference.GetComponent(this)) 95 | { 96 | Component->Activate(); 97 | } 98 | } 99 | 100 | ``` 101 | 102 | Code examples can be found in `BCRTestActor.h` 103 | 104 | Editor View: 105 | 106 | ![](Images/BCR-Large.png) 107 | 108 | Details View: 109 | 110 | ![](Images/BCR-Quick.png) 111 | 112 | Graph Nodes: 113 | 114 | ![](Images/BCR-Nodes.png) 115 | 116 | Example use: 117 | 118 | ![](Images/BCR-Hello.png) 119 | 120 | 121 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReference/BlueprintComponentReference.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class BlueprintComponentReference : ModuleRules 6 | { 7 | // This is to emulate engine installation and verify includes during development 8 | // Gives effect similar to BuildPlugin with -StrictIncludes 9 | public bool bStrictIncludesCheck = false; 10 | 11 | public BlueprintComponentReference(ReadOnlyTargetRules Target) : base(Target) 12 | { 13 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 14 | 15 | if (bStrictIncludesCheck) 16 | { 17 | bUseUnity = false; 18 | PCHUsage = PCHUsageMode.NoPCHs; 19 | // Enable additional checks used for Engine modules 20 | bTreatAsEngineModule = true; 21 | } 22 | 23 | PublicIncludePaths.Add(ModuleDirectory); 24 | 25 | PublicDependencyModuleNames.AddRange(new string[] { 26 | "Core", 27 | "CoreUObject", 28 | "Engine" 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReference/BlueprintComponentReference.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BlueprintComponentReference.h" 4 | #include "Modules/ModuleManager.h" 5 | #include "Components/ActorComponent.h" 6 | #include "Misc/EngineVersionComparison.h" 7 | #include "GameFramework/Actor.h" 8 | 9 | IMPLEMENT_MODULE(FDefaultModuleImpl, BlueprintComponentReference); 10 | 11 | FBlueprintComponentReference::FBlueprintComponentReference() 12 | : Mode(EBlueprintComponentReferenceMode::None) 13 | { 14 | } 15 | 16 | FBlueprintComponentReference::FBlueprintComponentReference(const FString& InValue) 17 | : Mode(EBlueprintComponentReferenceMode::None) 18 | { 19 | ParseString(InValue); 20 | } 21 | 22 | FBlueprintComponentReference::FBlueprintComponentReference(EBlueprintComponentReferenceMode InMode, const FName& InValue) 23 | : Mode(InMode), Value(InValue) 24 | { 25 | 26 | } 27 | 28 | bool FBlueprintComponentReference::ParseString(const FString& InValue) 29 | { 30 | FString ParsedMode, ParsedValue; 31 | if (InValue.Split(TEXT(":"), &ParsedMode, &ParsedValue, ESearchCase::CaseSensitive)) 32 | { 33 | if (ParsedMode.Equals(TEXT("property"), ESearchCase::IgnoreCase) 34 | || ParsedMode.Equals(TEXT("var"), ESearchCase::IgnoreCase)) // legacy compat 35 | { 36 | Mode = EBlueprintComponentReferenceMode::Property; 37 | Value = *ParsedValue.TrimEnd(); 38 | return true; 39 | } 40 | if (ParsedMode.Equals(TEXT("path"), ESearchCase::IgnoreCase)) 41 | { 42 | Mode = EBlueprintComponentReferenceMode::Path; 43 | Value = *ParsedValue.TrimEnd(); 44 | return true; 45 | } 46 | } 47 | else if (!InValue.IsEmpty()) 48 | { 49 | Mode = EBlueprintComponentReferenceMode::Property; 50 | Value = *InValue.TrimStartAndEnd(); 51 | return true; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | FString FBlueprintComponentReference::ToString() const 58 | { 59 | TStringBuilder<32> Result; 60 | switch(Mode) 61 | { 62 | case EBlueprintComponentReferenceMode::Property: 63 | Result.Append(TEXT("property:")); 64 | Result.Append(Value.ToString()); 65 | break; 66 | case EBlueprintComponentReferenceMode::Path: 67 | Result.Append(TEXT("path:")); 68 | Result.Append(Value.ToString()); 69 | break; 70 | default: 71 | break; 72 | } 73 | return Result.ToString(); 74 | } 75 | 76 | UActorComponent* FBlueprintComponentReference::GetComponent(AActor* SearchActor) const 77 | { 78 | UActorComponent* Result = nullptr; 79 | 80 | if(SearchActor) 81 | { 82 | switch (Mode) 83 | { 84 | case EBlueprintComponentReferenceMode::Property: 85 | // Variation 1: property name 86 | if(FObjectPropertyBase* ObjProp = FindFProperty(SearchActor->GetClass(), Value)) 87 | { 88 | Result = Cast(ObjProp->GetObjectPropertyValue_InContainer(SearchActor)); 89 | } 90 | break; 91 | case EBlueprintComponentReferenceMode::Path: 92 | // Variation 2: subobject path 93 | Result = FindObjectFast(SearchActor, Value); 94 | break; 95 | //case EBlueprintComponentReferenceMode::Dynamic: 96 | // Variation 3: dynamic selection 97 | //break; 98 | case EBlueprintComponentReferenceMode::None: 99 | default: 100 | break; 101 | } 102 | } 103 | 104 | return Result; 105 | } 106 | 107 | bool FBlueprintComponentReference::IsNull() const 108 | { 109 | return Value.IsNone() && Mode == EBlueprintComponentReferenceMode::None; 110 | } 111 | 112 | void FBlueprintComponentReference::Invalidate() 113 | { 114 | Mode = EBlueprintComponentReferenceMode::None; 115 | Value = NAME_None; 116 | } 117 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReference/BlueprintComponentReference.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "UObject/SoftObjectPtr.h" 6 | #include "Components/ActorComponent.h" 7 | #include "BlueprintComponentReference.generated.h" 8 | 9 | /** 10 | * Defines method which ComponentReference resolves the component from actor 11 | */ 12 | UENUM() 13 | enum class EBlueprintComponentReferenceMode : uint8 14 | { 15 | /** 16 | * Undefined referencing mode 17 | */ 18 | None UMETA(Hidden), 19 | /** 20 | * Referencing via property 21 | */ 22 | Property UMETA(DisplayName="Property Name"), 23 | /** 24 | * Referencing via object path 25 | */ 26 | Path UMETA(DisplayName="Object Path") 27 | }; 28 | 29 | /** 30 | * Struct that allows referencing actor components within blueprint. 31 | * 32 | * Component picker behavior customized via metadata specifiers. 33 | * 34 | * 35 | * Supported use cases: 36 | * - Class/Struct member property 37 | * - Blueprint or Local Blueprint Function variable 38 | * - Array property 39 | * - Set property 40 | * - Map property as Key or Value 41 | * 42 | *

Example code

43 | * @code 44 | * UPROPERTY(EditAnywhere, BlueprintReadOnly, meta=(ShowBlueprint=True, ShowNative=False, NoNavigate)) 45 | * FBlueprintComponentReference MyProperty; 46 | * @endcode 47 | * 48 | *

Component display and filtering:

49 | * 50 | * - ShowNative=bool
51 | * Should include native (C++ CreateDefaultSubobject) components in list? 52 | * Default: True 53 | * 54 | * - ShowBlueprint=bool
55 | * Should include blueprint (Simple Construction Script) components in list? 56 | * Default: True 57 | * 58 | * - ShowInstanced=bool
59 | * Should include instanced components in list? 60 | * Default: False 61 | * 62 | * - ShowHidden=bool
63 | * Should include instanced components that have no variable bound to? 64 | * Default: False 65 | * 66 | * - ShowEditor=bool
67 | * Should include editor-only components? 68 | * Default: True 69 | * 70 | * - AllowedClasses="/Script/Engine.ClassA,/Script/Engine.Class.B"
71 | * Specifies list of allowed base component types 72 | * 73 | * - DisallowedClasses="/Script/Engine.ClassA,/Script/Engine.Class.B"
74 | * Specifies list of disallowed base component types 75 | * 76 | * 77 | *

Miscellaneious:

78 | * 79 | * - ActorClass="/Script/Module.ClassName"
80 | * Specifies actor class that would be used as a source to suggest components in dropdown
81 | * This specifier is used only in scenarios when automatic discovery of actor type is not possible (Data Asset member) 82 | * or there is a need to enforce specific actor type
83 | * Prefer native actor classes over blueprints to avoid loading unnesessary assets. 84 | * 85 | * - NoClear
86 | * Disables "Clear" action, that resets value to default state 87 | * Default: False 88 | * 89 | * - NoNavigate
90 | * Disables "Navigate" action, that attempts to select component in Components View window 91 | * Default: False 92 | * 93 | * - NoPicker
94 | * Disables component picker functions, enables direct editing of inner properties. 95 | * Default: False 96 | * 97 | */ 98 | USTRUCT(BlueprintType, meta=(DisableSplitPin)) 99 | struct BLUEPRINTCOMPONENTREFERENCE_API FBlueprintComponentReference 100 | { 101 | GENERATED_BODY() 102 | public: 103 | /** 104 | * Default constructor 105 | */ 106 | FBlueprintComponentReference(); 107 | 108 | /** 109 | * Construct reference from smart path. 110 | * 111 | * If mode not specified "Variable" mode is used. 112 | */ 113 | explicit FBlueprintComponentReference(const FString& InValue); 114 | 115 | /** 116 | * Construct reference manually 117 | */ 118 | explicit FBlueprintComponentReference(EBlueprintComponentReferenceMode InMode, const FName& InValue); 119 | 120 | /** 121 | * Set reference value from string. 122 | * Value may be represented as a pair "mode:value" or "value" 123 | * 124 | * @param InValue string 125 | */ 126 | bool ParseString(const FString& InValue); 127 | 128 | /** 129 | * Get reference value as string 130 | * 131 | * 132 | * @return string 133 | */ 134 | FString ToString() const; 135 | 136 | /** 137 | * Get current component selection mode 138 | */ 139 | EBlueprintComponentReferenceMode GetMode() const 140 | { 141 | return Mode; 142 | } 143 | 144 | /** 145 | * Get current component value 146 | */ 147 | const FName& GetValue() const 148 | { 149 | return Value; 150 | } 151 | 152 | /** 153 | * Get the actual component pointer from this reference 154 | * 155 | * @param SearchActor Actor to perform search in 156 | */ 157 | UActorComponent* GetComponent(AActor* SearchActor) const; 158 | 159 | /** 160 | * Get the actual component pointer from this reference 161 | * 162 | * @param SearchActor Actor to perform search in 163 | */ 164 | template 165 | T* GetComponent(AActor* SearchActor) const 166 | { 167 | return Cast(GetComponent(SearchActor)); 168 | } 169 | 170 | /** 171 | * Does this reference have any value set 172 | */ 173 | bool IsNull() const; 174 | 175 | /** 176 | * Reset reference value to none 177 | */ 178 | void Invalidate(); 179 | 180 | bool operator==(const FBlueprintComponentReference& Rhs) const 181 | { 182 | return Mode == Rhs.Mode && Value == Rhs.Value; 183 | } 184 | 185 | bool operator!=(const FBlueprintComponentReference& Rhs) const 186 | { 187 | return !(*this == Rhs); 188 | } 189 | 190 | friend uint32 GetTypeHash(const FBlueprintComponentReference& A) 191 | { 192 | return HashCombine(GetTypeHash(A.Mode), GetTypeHash(A.Value)); 193 | } 194 | 195 | static FBlueprintComponentReference ForProperty(const FName& InName) 196 | { 197 | return FBlueprintComponentReference(EBlueprintComponentReferenceMode::Property, InName); 198 | } 199 | 200 | static FBlueprintComponentReference ForPath(const FName& InPath) 201 | { 202 | return FBlueprintComponentReference(EBlueprintComponentReferenceMode::Path, InPath); 203 | } 204 | 205 | protected: 206 | friend class FBlueprintComponentReferenceHelper; 207 | 208 | UPROPERTY(EditAnywhere, Category=Component) 209 | EBlueprintComponentReferenceMode Mode; 210 | 211 | UPROPERTY(EditAnywhere, Category=Component) 212 | FName Value; 213 | }; 214 | 215 | template<> 216 | struct TStructOpsTypeTraits 217 | : TStructOpsTypeTraitsBase2 218 | { 219 | enum 220 | { 221 | WithIdenticalViaEquality = true 222 | }; 223 | }; 224 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReference/BlueprintComponentReferenceLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BlueprintComponentReferenceLibrary.h" 4 | #include "GameFramework/Actor.h" 5 | 6 | inline bool TestComponentClass(UActorComponent* In, UClass* InClass) 7 | { 8 | return InClass ? In && In->IsA(InClass) : In != nullptr; 9 | } 10 | 11 | template 12 | bool ResolveComponentInternal(const TBase& Reference, AActor* Actor, UClass* Class, UActorComponent*& Component) 13 | { 14 | UActorComponent* Result = Reference.GetComponent(Actor); 15 | if (!TestComponentClass(Result, Class)) 16 | { 17 | Result = nullptr; 18 | } 19 | Component = Result; 20 | return Result != nullptr; 21 | } 22 | 23 | bool UBlueprintComponentReferenceLibrary::GetReferencedComponent(const FBlueprintComponentReference& Reference, AActor* Actor, TSubclassOf Class, UActorComponent*& Component) 24 | { 25 | return ResolveComponentInternal(Reference, Actor, Class, Component); 26 | } 27 | 28 | void UBlueprintComponentReferenceLibrary::TryGetReferencedComponent(const FBlueprintComponentReference& Reference, AActor* Actor, TSubclassOf Class, EComponentSearchResult& Result, UActorComponent*& Component) 29 | { 30 | Result = ResolveComponentInternal(Reference, Actor, Class, Component) ? EComponentSearchResult::Found : EComponentSearchResult::NotFound; 31 | } 32 | 33 | void UBlueprintComponentReferenceLibrary::GetReferencedComponents(const TArray& References, AActor* Actor, TSubclassOf Class, bool bKeepNulls, TArray& Components) 34 | { 35 | Components.Empty(); 36 | 37 | for (const FBlueprintComponentReference& Reference : References) 38 | { 39 | UActorComponent* Component = nullptr; 40 | ResolveComponentInternal(Reference, Actor, Class, Component); 41 | 42 | if (Component != nullptr || bKeepNulls) 43 | { 44 | Components.Add(Component); 45 | } 46 | } 47 | } 48 | 49 | void UBlueprintComponentReferenceLibrary::GetSetReferencedComponents(const TSet& References, AActor* Actor, TSubclassOf Class, TSet& Components) 50 | { 51 | Components.Empty(); 52 | 53 | for (const FBlueprintComponentReference& Reference : References) 54 | { 55 | UActorComponent* Component = nullptr; 56 | ResolveComponentInternal(Reference, Actor, Class, Component); 57 | 58 | if (Component != nullptr) 59 | { 60 | Components.Add(Component); 61 | } 62 | } 63 | } 64 | 65 | bool UBlueprintComponentReferenceLibrary::IsNullComponentReference(const FBlueprintComponentReference& Reference) 66 | { 67 | return Reference.IsNull(); 68 | } 69 | 70 | bool UBlueprintComponentReferenceLibrary::IsValidComponentReference(const FBlueprintComponentReference& Reference) 71 | { 72 | return !Reference.IsNull(); 73 | } 74 | 75 | void UBlueprintComponentReferenceLibrary::InvalidateComponentReference(FBlueprintComponentReference& Reference) 76 | { 77 | Reference.Invalidate(); 78 | } 79 | 80 | bool UBlueprintComponentReferenceLibrary::EqualEqual_ComponentReference(const FBlueprintComponentReference& A, const FBlueprintComponentReference& B) 81 | { 82 | return A == B; 83 | } 84 | 85 | bool UBlueprintComponentReferenceLibrary::NotEqual_ComponentReference(const FBlueprintComponentReference& A, const FBlueprintComponentReference& B) 86 | { 87 | return A != B; 88 | } 89 | 90 | FString UBlueprintComponentReferenceLibrary::Conv_ComponentReferenceToString(const FBlueprintComponentReference& Reference) 91 | { 92 | return Reference.ToString(); 93 | } 94 | 95 | bool UBlueprintComponentReferenceLibrary::Array_ContainsComponent(const TArray& TargetArray, UActorComponent* ItemToFind) 96 | { 97 | if(TargetArray.Num() && ItemToFind && ItemToFind->GetOwner()) 98 | { 99 | AActor* SearchTarget = ItemToFind->GetOwner(); 100 | for (const FBlueprintComponentReference& Reference : TargetArray) 101 | { 102 | if (Reference.GetComponent(SearchTarget) != nullptr) 103 | { 104 | return true; 105 | } 106 | } 107 | } 108 | return false; 109 | } 110 | 111 | bool UBlueprintComponentReferenceLibrary::Set_ContainsComponent(const TSet& TargetSet, UActorComponent* ItemToFind) 112 | { 113 | if(TargetSet.Num() && ItemToFind && ItemToFind->GetOwner()) 114 | { 115 | AActor* SearchTarget = ItemToFind->GetOwner(); 116 | for (const FBlueprintComponentReference& Reference : TargetSet) 117 | { 118 | if (Reference.GetComponent(SearchTarget) != nullptr) 119 | { 120 | return true; 121 | } 122 | } 123 | } 124 | return false; 125 | } 126 | 127 | bool UBlueprintComponentReferenceLibrary::Map_FindComponent_Impl(const void* TargetMap, const FMapProperty* MapProperty, const void* KeyPtr, void* OutValuePtr) 128 | { 129 | if (!MapProperty->KeyProp->IsA(FStructProperty::StaticClass()) || 130 | CastFieldChecked(MapProperty->KeyProp)->Struct != StaticStruct()) 131 | { 132 | FFrame::KismetExecutionMessage( 133 | *FString::Printf(TEXT("Attempted use 'FindComponentInRefMap' node with map '%s' that does not use 'FBlueprintComponentReference' key!"), 134 | *MapProperty->GetName()), ELogVerbosity::Error); 135 | return false; 136 | } 137 | 138 | const UActorComponent* SearchComponent = reinterpret_cast(KeyPtr); 139 | if(TargetMap && IsValid(SearchComponent) && IsValid(SearchComponent->GetOwner())) 140 | { 141 | AActor* SearchTarget = SearchComponent->GetOwner(); 142 | uint8* FoundValuePtr = nullptr; 143 | 144 | FScriptMapHelper MapHelper(MapProperty, TargetMap); 145 | 146 | for (FScriptMapHelper::FIterator It(MapHelper); It; ++It) 147 | { 148 | const FBlueprintComponentReference* pKey = reinterpret_cast(MapHelper.GetKeyPtr(It)); 149 | uint8* pValue = MapHelper.GetValuePtr(It); 150 | 151 | if (pKey->GetComponent(SearchTarget) == SearchComponent) 152 | { 153 | FoundValuePtr = pValue; 154 | break; 155 | } 156 | } 157 | 158 | if (OutValuePtr) 159 | { 160 | if (FoundValuePtr) 161 | { 162 | MapProperty->ValueProp->CopyCompleteValueFromScriptVM(OutValuePtr, FoundValuePtr); 163 | } 164 | else 165 | { 166 | MapProperty->ValueProp->InitializeValue(OutValuePtr); 167 | } 168 | } 169 | 170 | return FoundValuePtr != nullptr; 171 | } 172 | return false; 173 | } 174 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReference/BlueprintComponentReferenceLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "BlueprintComponentReference.h" 6 | #include "Kismet/BlueprintFunctionLibrary.h" 7 | #include "Kismet/BlueprintMapLibrary.h" 8 | #include "Components/ActorComponent.h" 9 | #include "BlueprintComponentReferenceLibrary.generated.h" 10 | 11 | UENUM() 12 | enum class EComponentSearchResult : uint8 13 | { 14 | Found, 15 | NotFound 16 | }; 17 | 18 | /** 19 | * Helper functions to interact with component references from blueprints 20 | */ 21 | UCLASS() 22 | class BLUEPRINTCOMPONENTREFERENCE_API UBlueprintComponentReferenceLibrary : public UBlueprintFunctionLibrary 23 | { 24 | GENERATED_BODY() 25 | public: 26 | /** 27 | * Resolve component reference in specified actor (impure). 28 | * 29 | * @param Reference Component reference to resolve 30 | * @param Actor Target actor 31 | * @param Class Expected component class (optional) 32 | * @param Result Output pin selector 33 | * @param Component Resolved component 34 | */ 35 | UFUNCTION(BlueprintCallable, Category="Utilities|ComponentReference", meta=(DisplayName="Find Referenced Component", DefaultToSelf="Actor", DeterminesOutputType="Class", DynamicOutputParam="Component", ExpandEnumAsExecs="Result", Keywords="GetReferencedComponent cref")) 36 | static void TryGetReferencedComponent(const FBlueprintComponentReference& Reference, AActor* Actor, TSubclassOf Class, EComponentSearchResult& Result, UActorComponent*& Component); 37 | 38 | /** 39 | * Resolve component reference in specified actor (pure). 40 | * 41 | * @param Reference Component reference to resolve 42 | * @param Actor Target actor 43 | * @param Class Expected component class 44 | * @param Component Resolved component 45 | * @return True if component found, False otherwise 46 | */ 47 | UFUNCTION(BlueprintPure, Category="Utilities|ComponentReference", meta=( DefaultToSelf="Actor", DeterminesOutputType="Class", DynamicOutputParam="Component", Keywords="cref")) 48 | static UPARAM(DisplayName="Success") bool GetReferencedComponent(const FBlueprintComponentReference& Reference, AActor* Actor, TSubclassOf Class, UActorComponent*& Component); 49 | 50 | /** 51 | * Resolve array of component references in specific actor 52 | * 53 | * @param References Component reference to resolve 54 | * @param Actor Target actor 55 | * @param Class Expected component class 56 | * @param bKeepNulls Preserve order if component resolve failed 57 | * @param Components Resolved components 58 | */ 59 | UFUNCTION(BlueprintCallable, Category="Utilities|ComponentReference|Containers", meta=(DisplayName="Get Referenced Components (Array)", DefaultToSelf="Actor", bKeepNulls=false, AdvancedDisplay=3, DeterminesOutputType="Class", DynamicOutputParam="Components", Keywords="cref")) 60 | static void GetReferencedComponents(const TArray& References, AActor* Actor, TSubclassOf Class, bool bKeepNulls, TArray& Components); 61 | 62 | /** 63 | * Resolve set of component references in specific actor 64 | * 65 | * @param References Component reference to resolve 66 | * @param Actor Target actor 67 | * @param Class Expected component class 68 | * @param bKeepNulls Preserve order if component resolve failed 69 | * @param Components Resolved components 70 | */ 71 | UFUNCTION(BlueprintCallable, Category="Utilities|ComponentReference|Containers", meta=(DisplayName="Get Referenced Components (Set)", DefaultToSelf="Actor", DeterminesOutputType="Class", DynamicOutputParam="Components", Keywords="cref")) 72 | static void GetSetReferencedComponents(const TSet& References, AActor* Actor, TSubclassOf Class, TSet& Components); 73 | 74 | /** 75 | * Does the component reference have no value set? 76 | * 77 | * @param Reference Input reference 78 | * @return True if reference have no value set 79 | */ 80 | UFUNCTION(BlueprintPure, Category="Utilities|ComponentReference", meta=(DisplayName="Is Null Component Reference", BlueprintThreadSafe, Keywords="cref")) 81 | static bool IsNullComponentReference(const FBlueprintComponentReference& Reference); 82 | 83 | /** 84 | * Does the component reference have any value set? 85 | * 86 | * @param Reference Input reference 87 | * @return True if reference have any value set 88 | */ 89 | UFUNCTION(BlueprintPure, Category="Utilities|ComponentReference", meta=(DisplayName="Is Valid Component Reference", BlueprintThreadSafe, Keywords="cref")) 90 | static bool IsValidComponentReference(const FBlueprintComponentReference& Reference); 91 | 92 | /** 93 | * Reset reference variable value to none 94 | * 95 | * @param Reference Input reference 96 | */ 97 | UFUNCTION(BlueprintCallable, Category="Utilities|ComponentReference", meta=(DisplayName="Invalidate Component Reference", Keywords="cref")) 98 | static void InvalidateComponentReference(UPARAM(Ref) FBlueprintComponentReference& Reference); 99 | 100 | /** Returns true if the values are equal (A == B) */ 101 | UFUNCTION(BlueprintPure, Category="Utilities|ComponentReference", meta=(DisplayName="Equal (BlueprintComponentReference)", CompactNodeTitle="==", BlueprintThreadSafe, Keywords = "== equal cref")) 102 | static bool EqualEqual_ComponentReference( const FBlueprintComponentReference& A, const FBlueprintComponentReference& B ); 103 | 104 | /** Returns true if the values are not equal (A != B) */ 105 | UFUNCTION(BlueprintPure, Category="Utilities|ComponentReference", meta=(DisplayName="Not Equal (BlueprintComponentReference)", CompactNodeTitle="!=", BlueprintThreadSafe, Keywords = "!= not equal cref")) 106 | static bool NotEqual_ComponentReference( const FBlueprintComponentReference& A, const FBlueprintComponentReference& B ); 107 | 108 | /** Convert reference to readable string */ 109 | UFUNCTION(BlueprintPure, Category="Utilities|ComponentReference", meta=(DisplayName="To String (BlueprintComponentReference)", CompactNodeTitle = "->", Keywords="cast convert cref", BlueprintThreadSafe, BlueprintAutocast)) 110 | static FString Conv_ComponentReferenceToString(const FBlueprintComponentReference& Reference); 111 | 112 | /** 113 | * Returns true if the array contains the given item 114 | * 115 | * Uses Component's Owner as Search Target to resolve components. 116 | * 117 | * @param TargetArray The array to search for the item 118 | * @param ItemToFind The item to look for 119 | * @return True if the item was found within the array 120 | */ 121 | UFUNCTION(BlueprintPure, meta=(DisplayName = "Contains Component (Array)", Category="Utilities|ComponentReference|Containers")) 122 | static bool Array_ContainsComponent(const TArray& TargetArray, UActorComponent* ItemToFind); 123 | 124 | /** 125 | * Returns true if set contains the given item 126 | * 127 | * Uses Component's Owner as Search Target to resolve components. 128 | * 129 | * @param TargetSet The set to search for the item 130 | * @param ItemToFind The item to look for 131 | * @return True if the item was found within the set 132 | */ 133 | UFUNCTION(BlueprintPure, meta=(DisplayName = "Contains Component (Set)", Category="Utilities|ComponentReference|Containers")) 134 | static bool Set_ContainsComponent(const TSet& TargetSet, UActorComponent* ItemToFind); 135 | 136 | /** 137 | * EXPERIMENTAL: Fast search of component within TMap for blueprint users 138 | * 139 | * WARNING: It is just a decorated loop due to need to invoke GetComponent on each component reference 140 | * to obtain referenced component for comparsion, but should be much faster than doing it in pure blueprint 141 | * and there is no need to use blueprint macro library ForEach/ForEachWithBreak 142 | * 143 | * Uses Component's Owner as Search Target to resolve components. 144 | * 145 | * @param TargetMap The map to perform the lookup on 146 | * @param Component Actor component to search 147 | * @param Value The value associated with the key, default constructed if key was not found 148 | * @return True if an item was found (False indicates nothing in the map uses the provided key) 149 | */ 150 | UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Find Component (Map)", Category="Utilities|ComponentReference|Containers", MapParam = "TargetMap", MapValueParam = "Value", AutoCreateRefTerm = "Value", BlueprintInternalUseOnly=true)) 151 | static bool Map_FindComponent(const TMap& TargetMap, UActorComponent* Component, int32& Value); 152 | 153 | // based on KismetMathLibrary::Map_Find 154 | // based on KismetMathLibrary::execMap_Find 155 | DECLARE_FUNCTION(execMap_FindComponent) 156 | { 157 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 158 | 159 | Stack.MostRecentProperty = nullptr; 160 | Stack.StepCompiledIn(NULL); 161 | void* MapAddr = Stack.MostRecentPropertyAddress; 162 | FMapProperty* MapProperty = CastField(Stack.MostRecentProperty); 163 | if (!MapProperty 164 | || !MapProperty->KeyProp->IsA(FStructProperty::StaticClass()) 165 | || !(CastFieldChecked(MapProperty->KeyProp)->Struct == StaticStruct())) 166 | { 167 | Stack.bArrayContextFailed = true; 168 | return; 169 | } 170 | 171 | // Key property is object ptr 172 | P_GET_OBJECT(UActorComponent, KeyPtr); 173 | 174 | // Since Value aren't really an int, step the stack manually 175 | const FProperty* CurrValueProp = MapProperty->ValueProp; 176 | const int32 ValuePropertySize = CurrValueProp->ElementSize * CurrValueProp->ArrayDim; 177 | void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize); 178 | CurrValueProp->InitializeValue(ValueStorageSpace); 179 | 180 | Stack.MostRecentPropertyAddress = nullptr; 181 | Stack.MostRecentPropertyContainer = nullptr; 182 | Stack.StepCompiledIn(ValueStorageSpace); 183 | const FFieldClass* CurrValuePropClass = CurrValueProp->GetClass(); 184 | const FFieldClass* MostRecentPropClass = Stack.MostRecentProperty->GetClass(); 185 | void* ItemPtr; 186 | // If the destination and the inner type are identical in size and their field classes derive from one another, 187 | // then permit the writing out of the array element to the destination memory 188 | if (Stack.MostRecentPropertyAddress != NULL 189 | && (ValuePropertySize == Stack.MostRecentProperty->ElementSize * Stack.MostRecentProperty->ArrayDim) 190 | && (MostRecentPropClass->IsChildOf(CurrValuePropClass) || CurrValuePropClass->IsChildOf(MostRecentPropClass))) 191 | { 192 | ItemPtr = Stack.MostRecentPropertyAddress; 193 | } 194 | else 195 | { 196 | ItemPtr = ValueStorageSpace; 197 | } 198 | 199 | P_FINISH; 200 | P_NATIVE_BEGIN; 201 | *(bool*)RESULT_PARAM = Map_FindComponent_Impl(MapAddr, MapProperty, KeyPtr, ItemPtr); 202 | P_NATIVE_END; 203 | 204 | CurrValueProp->DestroyValue(ValueStorageSpace); 205 | 206 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 207 | } 208 | 209 | private: 210 | // Implementation of Map_FindComponent 211 | static bool Map_FindComponent_Impl(const void* TargetMap, const FMapProperty* MapProperty, const void* KeyPtr, void* OutValuePtr); 212 | }; 213 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReference/CachedBlueprintComponentReference.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BlueprintComponentReference.h" 4 | #include "Containers/Map.h" 5 | #include "Containers/Array.h" 6 | #include "UObject/ObjectKey.h" 7 | #include "Misc/CoreMiscDefines.h" 8 | 9 | class AActor; 10 | class UActorComponent; 11 | class UObject; 12 | 13 | namespace BCRDetails 14 | { 15 | template 16 | struct TUnused {}; 17 | 18 | template 19 | struct TRawPtr 20 | { 21 | using ViewType = T; 22 | T* Value = nullptr; 23 | TRawPtr(T* Value = nullptr) : Value(Value) { } 24 | TRawPtr(TRawPtr&& Other) : Value(Other.Value) { } 25 | TRawPtr(const TRawPtr& Other) : Value(Other.Value) { } 26 | TRawPtr& operator=(const TRawPtr& Other) { Value = Other.Value; return *this; } 27 | TRawPtr& operator=(TRawPtr&& Other) { Value = Other.Value; return *this; } 28 | operator bool() const { return Value != nullptr; } 29 | void Reset() { Value = nullptr; } 30 | T* operator->() const { return Value; } 31 | T* Get() const { return Value; } 32 | T& operator*() const { return *Value; } 33 | }; 34 | 35 | template 36 | T* ResolvePointer(const TObjectPtr& InActor) { return InActor.Get(); } 37 | template 38 | T* ResolvePointer(const TWeakObjectPtr& InActor) { return InActor.Get(); } 39 | template 40 | T* ResolvePointer(const TRawPtr& InActor) { return InActor.Value; } 41 | 42 | template 43 | bool ValidatePointer(const TObjectPtr& InActor) { return InActor.Get() != nullptr; } 44 | template 45 | bool ValidatePointer(const TWeakObjectPtr& InActor) { return InActor.IsValid(); } 46 | template 47 | bool ValidatePointer(const TRawPtr& InActor) { return InActor.Get() != nullptr; } 48 | 49 | struct TSetKeyFuncs : DefaultKeyFuncs 50 | { 51 | using KeyInitType = typename DefaultKeyFuncs::KeyInitType; 52 | using ElementInitType = typename DefaultKeyFuncs::ElementInitType; 53 | }; 54 | 55 | template 56 | struct TMapKeyFuncs : TDefaultMapHashableKeyFuncs 57 | { 58 | using KeyInitType = typename TDefaultMapHashableKeyFuncs::KeyInitType; 59 | using ElementInitType = typename TDefaultMapHashableKeyFuncs::ElementInitType; 60 | using HashabilityCheck = typename TDefaultMapHashableKeyFuncs::HashabilityCheck; 61 | }; 62 | } 63 | 64 | #define BCR_DEFAULT_CONSTRUCTORS(TypeName) \ 65 | explicit TypeName(ENoInit) : Super(ENoInit::NoInit) { } \ 66 | explicit TypeName(TargetType* InTarget) : Super(InTarget, nullptr) { } \ 67 | explicit TypeName(AActor* InBaseActor, TargetType* InTarget) : Super(InTarget, InBaseActor) { } \ 68 | 69 | #define BCR_MOVE_ONLY_TYPE(TypeName) \ 70 | TypeName(const TypeName&) = delete; \ 71 | TypeName& operator=(const TypeName&) = delete; \ 72 | TypeName(TypeName&& Other) : Super(Forward(Other)) {} \ 73 | TypeName& operator=(TypeName&& Other) { Super::operator=(Forward(Other)); return *this; } \ 74 | 75 | /** 76 | * EXPERIMENTAL.
77 | * 78 | * A helper type that proxies FBlueprintComponentReference calls with a cached weak pointer to resolved component with explicit type. 79 | * 80 | * Goal is to provide caching of resolved value and type safety for usage in code, without adding cached pointer into original reference struct. 81 | * 82 | * Data source expected to be immutable and read-only (changes only in editor at design time, no runtime modification). 83 | * If original data ever changes in runtime - cache must be invalidated or changes synced by accessing Storage directly. 84 | * 85 | * Helpers wrap each common use: 86 | * 87 | * - TCachedComponentReference for single entry 88 | * - TCachedComponentReferenceArray for array entry 89 | * - TCachedComponentReferenceMap for map value 90 | * 91 | * @code 92 | * 93 | * UCLASS() 94 | * class AMyActorClass : public AActor 95 | * { 96 | * GENERATED_BODY() 97 | * public: 98 | * // single entry 99 | * 100 | * UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Test", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 101 | * FBlueprintComponentReference ReferenceSingle; 102 | * 103 | * TCachedComponentReference CachedReferenceSingle { this, &ReferenceSingle }; 104 | * 105 | * // array entry 106 | * 107 | * UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Test", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 108 | * TArray ReferenceArray; 109 | * 110 | * TCachedComponentReferenceArray CachedReferenceArray { this, &ReferenceArray }; 111 | * }; 112 | * 113 | * @endcode 114 | * 115 | */ 116 | class FCachedComponentReferenceBase 117 | { 118 | public: 119 | FCachedComponentReferenceBase() = default; 120 | ~FCachedComponentReferenceBase() = default; 121 | 122 | private: 123 | FCachedComponentReferenceBase(const FCachedComponentReferenceBase&) = delete; 124 | FCachedComponentReferenceBase& operator=(const FCachedComponentReferenceBase&) = delete; 125 | }; 126 | 127 | /** 128 | * Generic base 129 | * 130 | * @tparam Component Expected component type 131 | * @tparam Target Source reference type 132 | * @tparam Storage Resolved storage type 133 | * @tparam InternalPtr Internal pointer type 134 | */ 135 | template typename InternalPtr> 136 | class TCachedComponentReferenceBase : public FCachedComponentReferenceBase 137 | { 138 | public: 139 | using ComponentType = Component; 140 | using TargetType = Target; 141 | using StorageType = Storage; 142 | protected: 143 | // data source 144 | TargetType* InternalTarget; 145 | // storage for cached data 146 | mutable StorageType InternalStorage; 147 | // base actor for lookup 148 | InternalPtr BaseActor; 149 | public: 150 | TCachedComponentReferenceBase(ENoInit) 151 | : InternalTarget(nullptr), BaseActor(nullptr) 152 | { 153 | } 154 | 155 | TCachedComponentReferenceBase(TargetType* InTarget, AActor* InActor) 156 | : InternalTarget(InTarget), BaseActor(InActor) 157 | { 158 | } 159 | 160 | TCachedComponentReferenceBase(TCachedComponentReferenceBase&& Other) 161 | : InternalTarget(MoveTemp(Other.InternalTarget)) 162 | , InternalStorage(MoveTemp(Other.InternalStorage)) 163 | , BaseActor(MoveTemp(Other.BaseActor)) 164 | { 165 | } 166 | 167 | TCachedComponentReferenceBase& operator=(TCachedComponentReferenceBase&& Other) 168 | { 169 | InternalTarget = MoveTemp(Other.InternalTarget); 170 | InternalStorage = MoveTemp(Other.InternalStorage); 171 | BaseActor = MoveTemp(Other.BaseActor); 172 | return *this; 173 | } 174 | 175 | InternalPtr& GetBaseActor() { return BaseActor; } 176 | AActor* GetBaseActorPtr() const { return BCRDetails::ResolvePointer(BaseActor); } 177 | void SetBaseActor(AActor* InActor) { BaseActor = InActor; } 178 | 179 | TargetType& GetTarget() const { return *InternalTarget; } 180 | StorageType& GetStorage() /*fake*/const { return InternalStorage; } 181 | 182 | // TBD: with custom pointer types has to be managed externally 183 | void AddReferencedObjects(class FReferenceCollector& Collector, const UObject* ReferencingObject = nullptr) = delete; 184 | }; 185 | 186 | /** 187 | * EXPERIMENTAL.
188 | * 189 | * Templated wrapper over FBlueprintComponentReference that caches pointer to resolved objects. 190 | * 191 | * @code 192 | * TCachedComponentReferenceSingle CachedTargetCompA { this, &TargetComponent }; 193 | * TCachedComponentReferenceSingle CachedTargetCompB { &TargetComponent }; 194 | * @endcode 195 | * 196 | * @tparam Component Expected component type 197 | * @tparam InternalPtr Internal pointer type 198 | */ 199 | template typename InternalPtr = TWeakObjectPtr> 200 | class TCachedComponentReferenceSingle 201 | : public TCachedComponentReferenceBase, InternalPtr> 202 | { 203 | using Super = TCachedComponentReferenceBase, InternalPtr>; 204 | public: 205 | using StorageType = typename Super::StorageType; 206 | using TargetType = typename Super::TargetType; 207 | 208 | BCR_DEFAULT_CONSTRUCTORS(TCachedComponentReferenceSingle) 209 | BCR_MOVE_ONLY_TYPE(TCachedComponentReferenceSingle) 210 | 211 | template 212 | Component* Get() 213 | { 214 | return this->Get(this->GetBaseActorPtr()); 215 | } 216 | 217 | template 218 | Component* Get(AActor* InActor) 219 | { 220 | static_assert(std::is_base_of_v, "T must be a descendant of Component"); 221 | 222 | Component* Result = BCRDetails::ResolvePointer(this->GetStorage()); 223 | if (Result && Result->GetOwner() == InActor) 224 | { 225 | return Cast(Result); 226 | } 227 | 228 | Result = this->GetTarget().template GetComponent(InActor); 229 | this->GetStorage() = Result; 230 | return Cast(Result); 231 | } 232 | 233 | void WarmupCache(AActor* InActor) 234 | { 235 | if (!InActor) 236 | { 237 | InActor = this->GetBaseActorPtr(); 238 | } 239 | 240 | this->GetStorage() = this->GetTarget().template GetComponent(InActor); 241 | } 242 | 243 | void InvalidateCache() 244 | { 245 | this->GetStorage().Reset(); 246 | } 247 | 248 | template 249 | void AddReferencedObjects(class FReferenceCollector& Collector, const T* ReferencingObject = nullptr) 250 | { 251 | Collector.AddReferencedObject(this->GetBaseActor(), ReferencingObject); 252 | Collector.AddReferencedObject(this->GetStorage(), ReferencingObject); 253 | } 254 | }; 255 | 256 | template typename InternalPtr = TWeakObjectPtr> 257 | using TCachedComponentReference = TCachedComponentReferenceSingle; 258 | 259 | /** 260 | * EXPERIMENTAL.
261 | * 262 | * Templated wrapper over TArray that stores pointers to resolved objects. 263 | * 264 | * @code 265 | * UCLASS() 266 | * class AMyActorClass : public AActor 267 | * { 268 | * GENERATED_BODY() 269 | * public: 270 | * UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, AllowedClasses="/Script/Engine.SceneComponent") 271 | * TArray TargetComponents; 272 | * 273 | * TCachedComponentReferenceArray CachedTargetComp { this, &TargetComponents }; 274 | * }; 275 | * 276 | * @endcode 277 | * 278 | * @tparam Component Expected component type 279 | * @tparam InternalPtr Internal pointer type 280 | */ 281 | template typename InternalPtr = TWeakObjectPtr> 282 | class TCachedComponentReferenceArray 283 | : public TCachedComponentReferenceBase, TArray>, InternalPtr> 284 | { 285 | using Super = TCachedComponentReferenceBase, TArray>, InternalPtr>; 286 | public: 287 | using StorageType = typename Super::StorageType; 288 | using TargetType = typename Super::TargetType; 289 | 290 | BCR_DEFAULT_CONSTRUCTORS(TCachedComponentReferenceArray) 291 | BCR_MOVE_ONLY_TYPE(TCachedComponentReferenceArray) 292 | 293 | template 294 | T* Get(int32 Index) 295 | { 296 | return this->Get(this->GetBaseActorPtr(), Index); 297 | } 298 | 299 | template 300 | T* Get(AActor* InActor, int32 Index) 301 | { 302 | static_assert(std::is_base_of_v, "T must be a descendant of Component"); 303 | 304 | StorageType& Storage = this->GetStorage(); 305 | TargetType& Target = this->GetTarget(); 306 | 307 | if (Storage.Num() != Target.Num()) 308 | { 309 | Storage.Reset();//purge it all, dont try track modification 310 | Storage.SetNum(Target.Num()); 311 | } 312 | 313 | if (!Storage.IsValidIndex(Index)) 314 | { 315 | return nullptr; 316 | } 317 | 318 | auto& ElementRef = Storage[Index]; 319 | 320 | Component* Result = BCRDetails::ResolvePointer(ElementRef); 321 | if (Result && Result->GetOwner() == InActor) 322 | { 323 | return Cast(Result); 324 | } 325 | 326 | Result = Target[Index].template GetComponent(InActor); 327 | ElementRef = Result; 328 | return Cast(Result); 329 | } 330 | 331 | void WarmupCache(AActor* InActor) 332 | { 333 | if (!InActor) 334 | { 335 | InActor = this->GetBaseActorPtr(); 336 | } 337 | 338 | TargetType& Target = this->GetTarget(); 339 | StorageType& Storage = this->GetStorage(); 340 | Storage.SetNum(Target.Num()); 341 | 342 | for (int32 Index = 0, Num = Target.Num(); Index < Num; ++Index) 343 | { 344 | Storage[Index] = Target[Index].template GetComponent(InActor); 345 | } 346 | } 347 | 348 | /** reset cached component */ 349 | void InvalidateCache(int32 Index = -1) 350 | { 351 | StorageType& Storage = this->GetStorage(); 352 | if (Storage.IsValidIndex(Index)) 353 | { 354 | Storage[Index] = nullptr; 355 | } 356 | else 357 | { 358 | Storage.Reset(); 359 | } 360 | } 361 | 362 | int32 Num() const 363 | { 364 | return this->GetTarget().Num(); 365 | } 366 | 367 | bool IsEmpty() const 368 | { 369 | return this->GetTarget().Num() == 0; 370 | } 371 | 372 | template 373 | void AddReferencedObjects(class FReferenceCollector& Collector, const T* ReferencingObject = nullptr) 374 | { 375 | Collector.AddReferencedObject(this->GetBaseActor(), ReferencingObject); 376 | for (auto& ElementRef : this->GetStorage()) 377 | { 378 | Collector.AddReferencedObject(ElementRef, ReferencingObject); 379 | } 380 | } 381 | }; 382 | 383 | /** 384 | * EXPERIMENTAL.
385 | * 386 | * Templated wrapper over TMap that stores pointers to resolved objects 387 | * 388 | * @code 389 | * UCLASS() 390 | * class AMyActorClass : public AActor 391 | * { 392 | * GENERATED_BODY() 393 | * public: 394 | * UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, AllowedClasses="/Script/Engine.SceneComponent") 395 | * TMap TargetComponents; 396 | * 397 | * TCachedComponentReferenceMapValue CachedTargetComp { this, &TargetComponents }; 398 | * }; 399 | * 400 | * @endcode 401 | * 402 | * @tparam Component Expected component type 403 | * @tparam Key Map key type 404 | * @tparam InternalPtr Type of internal pointer 405 | */ 406 | template typename InternalPtr = TWeakObjectPtr> 407 | class TCachedComponentReferenceMapValue 408 | : public TCachedComponentReferenceBase, TMap>, InternalPtr> 409 | { 410 | using Super = TCachedComponentReferenceBase, TMap>, InternalPtr>; 411 | public: 412 | using StorageType = typename Super::StorageType; 413 | using TargetType = typename Super::TargetType; 414 | 415 | BCR_DEFAULT_CONSTRUCTORS(TCachedComponentReferenceMapValue) 416 | BCR_MOVE_ONLY_TYPE(TCachedComponentReferenceMapValue) 417 | 418 | template 419 | Component* Get(const Key& InKey) 420 | { 421 | return this->Get(this->GetBaseActorPtr(), InKey); 422 | } 423 | 424 | template 425 | Component* Get(AActor* InActor, const Key& InKey) 426 | { 427 | static_assert(std::is_base_of_v, "T must be a descendant of Component"); 428 | 429 | TargetType& Target = this->GetTarget(); 430 | StorageType& Storage = this->GetStorage(); 431 | 432 | auto& ElementRef = Storage.FindOrAdd(InKey, {}); 433 | 434 | Component* Result = BCRDetails::ResolvePointer(ElementRef); 435 | if (Result && Result->GetOwner() == InActor) 436 | { 437 | return Cast(Result); 438 | } 439 | 440 | if (const FBlueprintComponentReference* Ref = Target.Find(InKey)) 441 | { 442 | Result = Ref->GetComponent(InActor); 443 | ElementRef = Result; 444 | } 445 | else 446 | { 447 | Result = nullptr; 448 | ElementRef = nullptr; 449 | } 450 | 451 | return Cast(Result); 452 | } 453 | 454 | void WarmupCache(AActor* InActor) 455 | { 456 | if (!InActor) 457 | { 458 | InActor = this->GetBaseActorPtr(); 459 | } 460 | 461 | TargetType& Target = this->GetTarget(); 462 | StorageType& Storage = this->GetStorage(); 463 | 464 | for (auto& KeyToRef : Target) 465 | { 466 | Component* Resolved = KeyToRef.Value.template GetComponent(InActor); 467 | 468 | Storage.Add(KeyToRef.Key, Resolved); 469 | } 470 | } 471 | 472 | void InvalidateCache() 473 | { 474 | this->GetStorage().Reset(); 475 | } 476 | 477 | int32 Num() const 478 | { 479 | return this->GetTarget().Num(); 480 | } 481 | 482 | bool IsEmpty() const 483 | { 484 | return this->GetTarget().Num() == 0; 485 | } 486 | 487 | template 488 | void AddReferencedObjects(class FReferenceCollector& Collector, const T* ReferencingObject = nullptr) 489 | { 490 | Collector.AddReferencedObject(this->GetBaseActor(), ReferencingObject); 491 | for (auto& ElementRef : this->GetStorage()) 492 | { 493 | Collector.AddReferencedObject(ElementRef.Value, ReferencingObject); 494 | } 495 | } 496 | }; 497 | 498 | 499 | /** 500 | * EXPERIMENTAL.
501 | * 502 | * Templated wrapper over TMap that stores pointers to resolved objects. 503 | * 504 | * This version always using TObjectKey for internal storage key. 505 | * 506 | * @code 507 | * UCLASS() 508 | * class AMyActorClass : public AActor 509 | * { 510 | * GENERATED_BODY() 511 | * public: 512 | * UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, AllowedClasses="/Script/Engine.SceneComponent") 513 | * TMap TargetComponents; 514 | * 515 | * TCachedComponentReferenceMapKey CachedTargetComp { this, &TargetComponents }; 516 | * }; 517 | * 518 | * @endcode 519 | * 520 | * @tparam Component Expected component type 521 | * @tparam Value Map key type 522 | */ 523 | template typename InternalPtr = TWeakObjectPtr> 524 | class TCachedComponentReferenceMapKey 525 | : public TCachedComponentReferenceBase, TMap, Value*>, InternalPtr> 526 | { 527 | using Super = TCachedComponentReferenceBase, TMap, Value*>, InternalPtr>; 528 | public: 529 | using StorageType = typename Super::StorageType; 530 | using TargetType = typename Super::TargetType; 531 | 532 | BCR_DEFAULT_CONSTRUCTORS(TCachedComponentReferenceMapKey) 533 | BCR_MOVE_ONLY_TYPE(TCachedComponentReferenceMapKey) 534 | 535 | Value* Get(const Component* InKey) 536 | { 537 | return this->Get(this->GetBaseActorPtr(), InKey); 538 | } 539 | 540 | Value* Get(AActor* InActor, const Component* InKey) 541 | { 542 | if (InActor && InKey && InActor != InKey->GetOwner()) 543 | { // no point in search in bad context 544 | return nullptr; 545 | } 546 | if (!InActor && InKey) 547 | { // pull actor from component 548 | InActor = InKey->GetOwner(); 549 | } 550 | 551 | TargetType& Target = this->GetTarget(); 552 | StorageType& Storage = this->GetStorage(); 553 | 554 | // if Storage is Ptr->BCR 555 | // cache hit would require to do lookup to ensure taking appropriate data from target 556 | // cached raw ptr -> cref 557 | // target cref -> value 558 | // cache miss would require resolve loop to find who references input component 559 | ///////////////////////////////// 560 | // if Storage is Ptr->Value Ptr 561 | // cache hit will give value pointer 562 | // cache miss will require resolve loop 563 | Value* FoundValue = nullptr; 564 | 565 | const auto* StoragePtr = Storage.Find(InKey); 566 | if (StoragePtr != nullptr) 567 | { 568 | FoundValue = *StoragePtr;// Target.Find(*StoragePtr); 569 | } 570 | else 571 | { // if nothing found the only way is to do loop and find our component 572 | for (auto& RefToValue : Target) 573 | { 574 | const FBlueprintComponentReference& Ref = RefToValue.Key; 575 | if (Ref.GetComponent(InActor) == InKey) 576 | { 577 | Storage.Add(InKey, &RefToValue.Value); 578 | 579 | FoundValue = &RefToValue.Value; 580 | break; 581 | } 582 | } 583 | } 584 | 585 | return FoundValue; 586 | } 587 | 588 | void WarmupCache(AActor* InActor) 589 | { 590 | if (!InActor) 591 | { 592 | InActor = this->GetBaseActorPtr(); 593 | } 594 | 595 | TargetType& Target = this->GetTarget(); 596 | StorageType& Storage = this->GetStorage(); 597 | 598 | for (auto& RefToValue : Target) 599 | { 600 | Component* Resolved = RefToValue.Key.template GetComponent(InActor); 601 | if (Resolved) 602 | { 603 | Storage.Add(Resolved, &RefToValue.Value); 604 | } 605 | } 606 | } 607 | 608 | void InvalidateCache() 609 | { 610 | this->GetStorage().Reset(); 611 | } 612 | 613 | int32 Num() const 614 | { 615 | return this->GetTarget().Num(); 616 | } 617 | 618 | bool IsEmpty() const 619 | { 620 | return this->GetTarget().Num() == 0; 621 | } 622 | 623 | template 624 | void AddReferencedObjects(class FReferenceCollector& Collector, const T* ReferencingObject = nullptr) 625 | { 626 | Collector.AddReferencedObject(this->GetBaseActor(), ReferencingObject); 627 | } 628 | }; 629 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceCustomization.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "BlueprintComponentReference.h" 6 | #include "BlueprintComponentReferenceMetadata.h" 7 | #include "BlueprintComponentReferenceHelper.h" 8 | #include "IDetailCustomNodeBuilder.h" 9 | #include "IPropertyTypeCustomization.h" 10 | #include "PropertyHandle.h" 11 | #include "Containers/Array.h" 12 | #include "PropertyEditorModule.h" 13 | #include "Internationalization/Text.h" 14 | #include "Styling/SlateBrush.h" 15 | #include "Templates/SharedPointer.h" 16 | 17 | class FMenuBuilder; 18 | class SComboButton; 19 | class FDragDropEvent; 20 | 21 | // Enable Drag&Drop function on a customization. Requires engine patch. 22 | // See FBlueprintComponentReferenceCustomization::OnVerifyDrag definition for details 23 | #ifndef WITH_BCR_DRAG_DROP 24 | #define WITH_BCR_DRAG_DROP 0 25 | #endif 26 | 27 | /** 28 | * Component reference cutomization class 29 | */ 30 | class FBlueprintComponentReferenceCustomization : public IPropertyTypeCustomization 31 | { 32 | public: 33 | /** Makes a new instance of this customization for a specific detail view requesting it */ 34 | static TSharedRef MakeInstance(); 35 | 36 | virtual void CustomizeHeader(TSharedRef InPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& PropertyTypeCustomizationUtils) override; 37 | virtual void CustomizeChildren(TSharedRef InPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& PropertyTypeCustomizationUtils) override; 38 | 39 | private: 40 | /** Build a simple debug context for the property */ 41 | FString GetLoggingContextString() const; 42 | FString CachedContextString; 43 | 44 | /** Build the combobox widget. */ 45 | void BuildComboBox(); 46 | 47 | /** 48 | * Determine the context the customization is used in 49 | */ 50 | void DetermineContext(); 51 | 52 | /** 53 | * Set the value of the asset referenced by this property editor. 54 | * Will set the underlying property handle if there is one. 55 | */ 56 | void SetValue(const FBlueprintComponentReference& Value); 57 | 58 | /** Get the value referenced by this widget. */ 59 | FPropertyAccess::Result GetValue(FBlueprintComponentReference& OutValue) const; 60 | 61 | /** Callback when the property value changed. */ 62 | void OnPropertyValueChanged(FName Source); 63 | 64 | bool IsComponentReferenceValid(const FBlueprintComponentReference& Value) const; 65 | 66 | bool CanEdit() const; 67 | bool CanEditChildren() const; 68 | 69 | const FSlateBrush* GetComponentIcon() const; 70 | FText OnGetComponentName() const; 71 | FSlateColor OnGetComponentNameColor() const; 72 | FText OnGetComponentTooltip() const; 73 | const FSlateBrush* GetStatusIcon() const; 74 | 75 | TSharedRef OnGetMenuContent(); 76 | void OnMenuOpenChanged(bool bOpen); 77 | 78 | void OnClear(); 79 | void OnNavigateComponent(); 80 | void OnComponentSelected(TSharedPtr Node); 81 | 82 | void CloseComboButton(); 83 | 84 | void ResetViewSettings(); 85 | bool TestNode(const TSharedPtr& Node) const; 86 | bool TestObject(const UObject* Object) const; 87 | 88 | #if WITH_BCR_DRAG_DROP 89 | bool OnVerifyDrag(TSharedPtr InDragDrop); 90 | FReply OnDropped(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent); 91 | FReply OnDrop(TSharedPtr InDragDrop); 92 | #endif 93 | 94 | private: 95 | /** The property handle we are customizing */ 96 | TSharedPtr PropertyHandle; 97 | /** Cached hierarchy utilities */ 98 | TSharedPtr ClassHelper; 99 | 100 | /** Main combo button */ 101 | TSharedPtr ComponentComboButton; 102 | 103 | /** Container with customization view settings */ 104 | FBlueprintComponentReferenceMetadata ViewSettings; 105 | 106 | /** component picker helper */ 107 | TSharedPtr ComponentPickerContext; 108 | 109 | enum class EPropertyState 110 | { 111 | // no errors 112 | Normal, 113 | // failed to access property data 114 | BadPropertyAccess, 115 | // value does not match filters 116 | BadReference, 117 | // value points to unknown component 118 | BadInfo, 119 | }; 120 | /** represents current state of customization since last update */ 121 | EPropertyState PropertyState = EPropertyState::Normal; 122 | /** currently selected node */ 123 | TWeakPtr CachedComponentNode; 124 | 125 | struct FSelectionData 126 | { 127 | TSharedPtr Category; 128 | TArray> Elements; 129 | }; 130 | TArray CachedChoosableElements; 131 | }; 132 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class BlueprintComponentReferenceEditor : ModuleRules 6 | { 7 | // This is to emulate engine installation and verify includes during development 8 | // Gives effect similar to BuildPlugin with -StrictIncludes 9 | public bool bStrictIncludesCheck = false; 10 | 11 | public BlueprintComponentReferenceEditor(ReadOnlyTargetRules Target) : base(Target) 12 | { 13 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 14 | 15 | if (bStrictIncludesCheck) 16 | { 17 | bUseUnity = false; 18 | PCHUsage = PCHUsageMode.NoPCHs; 19 | // Enable additional checks used for Engine modules 20 | bTreatAsEngineModule = true; 21 | } 22 | 23 | PublicIncludePaths.Add(ModuleDirectory); 24 | 25 | PublicDependencyModuleNames.AddRange(new string[] { 26 | "Core", 27 | "CoreUObject", 28 | "BlueprintComponentReference" 29 | }); 30 | 31 | PrivateDependencyModuleNames.AddRange(new string[] { 32 | "Engine", 33 | "Slate", 34 | "SlateCore", 35 | "UnrealEd", 36 | "EditorWidgets", 37 | "PropertyEditor", 38 | "ApplicationCore", 39 | "Kismet", 40 | "BlueprintGraph" 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BlueprintComponentReferenceEditor.h" 4 | #include "BlueprintComponentReferenceCustomization.h" 5 | #include "BlueprintComponentReferenceVarCustomization.h" 6 | #include "BlueprintEditorModule.h" 7 | #include "HAL/IConsoleManager.h" 8 | #include "UnrealEdGlobals.h" 9 | #include "Editor/EditorEngine.h" 10 | 11 | IMPLEMENT_MODULE(FBCREditorModule, BlueprintComponentReferenceEditor); 12 | 13 | DEFINE_LOG_CATEGORY(LogComponentReferenceEditor); 14 | 15 | #if ALLOW_CONSOLE 16 | 17 | static FAutoConsoleCommand BCR_DumpInstances( 18 | TEXT("BCR.DumpInstances"), 19 | TEXT("Dump active instance data"), 20 | FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& InArgs) { 21 | FBCREditorModule::GetReflectionHelper()->DebugDumpInstances(InArgs); 22 | }) 23 | ); 24 | static FAutoConsoleCommand BCR_DumpClasses( 25 | TEXT("BCR.DumpClasses"), 26 | TEXT("Dump active class data"), 27 | FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& InArgs) { 28 | FBCREditorModule::GetReflectionHelper()->DebugDumpClasses(InArgs); 29 | }) 30 | ); 31 | static FAutoConsoleCommand BCR_DumpContexts( 32 | TEXT("BCR.DumpContexts"), 33 | TEXT("Dump active contexts data"), 34 | FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& InArgs) { 35 | FBCREditorModule::GetReflectionHelper()->DebugDumpContexts(InArgs); 36 | }) 37 | ); 38 | static FAutoConsoleCommand BCR_ForceCleanup( 39 | TEXT("BCR.ForceCleanup"), 40 | TEXT("Force cleanup stale data"), 41 | FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& InArgs) { 42 | FBCREditorModule::GetReflectionHelper()->DebugForceCleanup(); 43 | }) 44 | ); 45 | static FAutoConsoleCommand BCR_EnableLogging( 46 | TEXT("BCR.EnableLogging"), 47 | TEXT("Enable BCR debug logging"), 48 | FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& InArgs) { 49 | LogComponentReferenceEditor.SetVerbosity(ELogVerbosity::VeryVerbose); 50 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("Enabled BCR debug logging")); 51 | }) 52 | ); 53 | 54 | #endif 55 | 56 | void FBCREditorModule::StartupModule() 57 | { 58 | if (GIsEditor && !IsRunningCommandlet()) 59 | { 60 | ClassHelper = MakeShared(); 61 | 62 | PostEngineInitHandle = FCoreDelegates::OnPostEngineInit.AddRaw(this, &FBCREditorModule::OnPostEngineInit); 63 | } 64 | } 65 | 66 | void FBCREditorModule::OnPostEngineInit() 67 | { 68 | FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitHandle); 69 | 70 | OnReloadCompleteDelegateHandle = FCoreUObjectDelegates::ReloadCompleteDelegate.AddRaw(this, &FBCREditorModule::OnReloadComplete); 71 | OnReloadReinstancingCompleteDelegateHandle = FCoreUObjectDelegates::ReloadReinstancingCompleteDelegate.AddRaw(this, &FBCREditorModule::OnReinstancingComplete); 72 | OnModulesChangedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddRaw(this, &FBCREditorModule::OnModulesChanged); 73 | OnBlueprintCompiledHandle = GEditor->OnBlueprintCompiled().AddRaw(this, &FBCREditorModule::OnBlueprintRecompile); 74 | 75 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 76 | PropertyModule.RegisterCustomPropertyTypeLayout( 77 | "BlueprintComponentReference", 78 | FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FBlueprintComponentReferenceCustomization::MakeInstance)); 79 | 80 | FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::GetModuleChecked("Kismet"); 81 | VariableCustomizationHandle = BlueprintEditorModule.RegisterVariableCustomization( 82 | FProperty::StaticClass(), 83 | FOnGetVariableCustomizationInstance::CreateStatic(&FBlueprintComponentReferenceVarCustomization::MakeInstance)); 84 | } 85 | 86 | void FBCREditorModule::ShutdownModule() 87 | { 88 | if (GIsEditor && !IsRunningCommandlet()) 89 | { 90 | FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitHandle); 91 | FCoreUObjectDelegates::ReloadCompleteDelegate.Remove(OnReloadCompleteDelegateHandle); 92 | FCoreUObjectDelegates::ReloadReinstancingCompleteDelegate.Remove(OnReloadReinstancingCompleteDelegateHandle); 93 | FModuleManager::Get().OnModulesChanged().Remove(OnModulesChangedDelegateHandle); 94 | 95 | if (GEditor) 96 | { 97 | GEditor->OnBlueprintCompiled().Remove(OnBlueprintCompiledHandle); 98 | } 99 | 100 | if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) 101 | { 102 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 103 | PropertyModule.UnregisterCustomPropertyTypeLayout("BlueprintComponentReference"); 104 | } 105 | 106 | if (FModuleManager::Get().IsModuleLoaded("Kismet")) 107 | { 108 | FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::GetModuleChecked("Kismet"); 109 | BlueprintEditorModule.UnregisterVariableCustomization(FProperty::StaticClass(), VariableCustomizationHandle); 110 | } 111 | } 112 | } 113 | 114 | TSharedPtr FBCREditorModule::GetReflectionHelper() 115 | { 116 | static const FName ModuleName("BlueprintComponentReferenceEditor"); 117 | auto& Ref = FModuleManager::LoadModuleChecked(ModuleName).ClassHelper; 118 | if (!Ref.IsValid()) 119 | { 120 | Ref = MakeShared(); 121 | } 122 | return Ref; 123 | } 124 | 125 | void FBCREditorModule::OnReloadComplete(EReloadCompleteReason ReloadCompleteReason) 126 | { 127 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("OnReloadComplete")); 128 | if (ClassHelper) 129 | { 130 | ClassHelper->CleanupStaleData(); 131 | ClassHelper->MarkBlueprintCacheDirty(); 132 | } 133 | } 134 | 135 | void FBCREditorModule::OnReinstancingComplete() 136 | { 137 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("OnReinstancingComplete")); 138 | if (ClassHelper) 139 | { 140 | ClassHelper->CleanupStaleData(); 141 | //ClassHelper->MarkBlueprintCacheDirty(); 142 | } 143 | } 144 | 145 | void FBCREditorModule::OnModulesChanged(FName Name, EModuleChangeReason ModuleChangeReason) 146 | { 147 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("OnModulesChanged")); 148 | if (ClassHelper) 149 | { 150 | ClassHelper->CleanupStaleData(); 151 | //ClassHelper->MarkBlueprintCacheDirty(); 152 | } 153 | } 154 | 155 | void FBCREditorModule::OnBlueprintRecompile() 156 | { 157 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("OnBlueprintRecompile")); 158 | if (ClassHelper) 159 | { 160 | ClassHelper->CleanupStaleData(); 161 | ClassHelper->MarkBlueprintCacheDirty(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleInterface.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FBlueprintComponentReferenceHelper; 9 | enum class EReloadCompleteReason; 10 | 11 | struct FBCREditorModule : public IModuleInterface 12 | { 13 | static TSharedPtr GetReflectionHelper(); 14 | 15 | virtual void StartupModule() override; 16 | virtual void ShutdownModule() override; 17 | virtual bool SupportsDynamicReloading() override { return false; } 18 | 19 | private: 20 | void OnPostEngineInit(); 21 | void OnReloadComplete(EReloadCompleteReason ReloadCompleteReason); 22 | void OnReinstancingComplete(); 23 | void OnModulesChanged(FName Name, EModuleChangeReason ModuleChangeReason); 24 | void OnBlueprintRecompile(); 25 | private: 26 | TSharedPtr ClassHelper; 27 | 28 | FDelegateHandle VariableCustomizationHandle; 29 | FDelegateHandle PostEngineInitHandle; 30 | 31 | FDelegateHandle OnReloadCompleteDelegateHandle; 32 | FDelegateHandle OnReloadReinstancingCompleteDelegateHandle; 33 | FDelegateHandle OnModulesChangedDelegateHandle; 34 | FDelegateHandle OnBlueprintCompiledHandle; 35 | }; 36 | 37 | DECLARE_LOG_CATEGORY_EXTERN(LogComponentReferenceEditor, Log, All); 38 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceHelper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BlueprintComponentReferenceHelper.h" 4 | 5 | #include "BlueprintComponentReferenceEditor.h" 6 | #include "Engine/World.h" 7 | #include "GameFramework/Pawn.h" 8 | #include "Kismet2/ComponentEditorUtils.h" 9 | #include "Misc/CoreDelegates.h" 10 | #include "Modules/ModuleManager.h" 11 | #include "UObject/UObjectIterator.h" 12 | #include "UObject/PropertyOptional.h" 13 | #include "Misc/EngineVersionComparison.h" 14 | #include "HAL/IConsoleManager.h" 15 | 16 | #define LOCTEXT_NAMESPACE "BlueprintComponentReference" 17 | 18 | static bool GBCRCacheEnabled = true; 19 | 20 | #if ALLOW_CONSOLE 21 | static FAutoConsoleVariableRef BCR_CacheEnabled_Var( 22 | TEXT("BCR.CacheEnabled"), GBCRCacheEnabled, 23 | TEXT("Enable BCR caching of instance and class data") 24 | ); 25 | #endif 26 | 27 | static FBlueprintComponentReferenceHelper::FInstanceKey MakeInstanceKey(const AActor* InActor) 28 | { 29 | FString FullKey = InActor ? FObjectPropertyBase::GetExportPath(InActor) : TEXT(""); 30 | return FBlueprintComponentReferenceHelper::FInstanceKey { FName(*FullKey), GetFNameSafe(InActor), GetFNameSafe(InActor->GetClass()) }; 31 | } 32 | 33 | static FBlueprintComponentReferenceHelper::FClassKey MakeClassKey(const UClass* InClass) 34 | { 35 | FString FullKey = InClass ? FObjectPropertyBase::GetExportPath(InClass) : TEXT(""); 36 | return FBlueprintComponentReferenceHelper::FClassKey { FName(*FullKey), GetFNameSafe(InClass) }; 37 | } 38 | 39 | inline static FString BuildComponentInfo(const UActorComponent* Obj) 40 | { 41 | TStringBuilder<256> Base; 42 | Base.Appendf(TEXT("%p:%s %s"), Obj, *Obj->GetName(), *Obj->GetClass()->GetName()); 43 | Base.Appendf(TEXT(" Flags=%d"), Obj->GetFlags()); 44 | Base.Appendf(TEXT(" Method=%s"), *StaticEnum()->GetNameStringByValue((int64)Obj->CreationMethod)); 45 | return Base.ToString(); 46 | } 47 | 48 | UActorComponent* FComponentInfo::GetComponentTemplate() const 49 | { 50 | return Object.Get(); 51 | } 52 | 53 | UClass* FComponentInfo::GetComponentClass() const 54 | { 55 | if (ObjectClass.IsValid()) 56 | { 57 | return ObjectClass.Get(); 58 | } 59 | return Object.IsValid() ? Object->GetClass() : nullptr; 60 | } 61 | 62 | FName FComponentInfo::GetNodeID() const 63 | { 64 | FName ItemName = GetVariableName(); 65 | if (ItemName == NAME_None) 66 | { 67 | UActorComponent* ComponentTemplateOrInstance = GetComponentTemplate(); 68 | if (ComponentTemplateOrInstance != nullptr) 69 | { 70 | ItemName = ComponentTemplateOrInstance->GetFName(); 71 | } 72 | } 73 | return ItemName; 74 | } 75 | 76 | // Custom version that will test instances as well as CDO 77 | static FName FComponentEditorUtils_FindVariableNameGivenComponentInstance(const UActorComponent* ComponentInstance) 78 | { 79 | check(ComponentInstance != nullptr); 80 | 81 | // When names mismatch, try finding a differently named variable pointing to the the component (the mismatch should only be possible for native components) 82 | auto FindPropertyReferencingComponent = [](const UActorComponent* Component, bool bUseInstance) -> FProperty* 83 | { 84 | if (AActor* OwnerActor = Component->GetOwner()) 85 | { 86 | UClass* OwnerClass = OwnerActor->GetClass(); 87 | AActor* SearchTarget = bUseInstance ? OwnerActor : CastChecked(OwnerClass->GetDefaultObject()); 88 | 89 | for (TFieldIterator PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) 90 | { 91 | FObjectProperty* TestProperty = *PropIt; 92 | if (Component->GetClass()->IsChildOf(TestProperty->PropertyClass)) 93 | { 94 | void* TestPropertyInstanceAddress = TestProperty->ContainerPtrToValuePtr(SearchTarget); 95 | UObject* ObjectPointedToByProperty = TestProperty->GetObjectPropertyValue(TestPropertyInstanceAddress); 96 | if (ObjectPointedToByProperty == Component) 97 | { 98 | // This property points to the component archetype, so it's an anchor even if it was named wrong 99 | return TestProperty; 100 | } 101 | } 102 | } 103 | 104 | // do not lookup in arrays. 105 | // it will break GetNodeId naming if many components found in one 106 | /* 107 | for (TFieldIterator PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) 108 | { 109 | FArrayProperty* TestProperty = *PropIt; 110 | void* ArrayPropInstAddress = TestProperty->ContainerPtrToValuePtr(SearchTarget); 111 | 112 | FObjectProperty* ArrayEntryProp = CastField(TestProperty->Inner); 113 | if ((ArrayEntryProp == nullptr) || !ArrayEntryProp->PropertyClass->IsChildOf()) 114 | { 115 | continue; 116 | } 117 | 118 | FScriptArrayHelper ArrayHelper(TestProperty, ArrayPropInstAddress); 119 | for (int32 ComponentIndex = 0; ComponentIndex < ArrayHelper.Num(); ++ComponentIndex) 120 | { 121 | UObject* ArrayElement = ArrayEntryProp->GetObjectPropertyValue(ArrayHelper.GetRawPtr(ComponentIndex)); 122 | if (ArrayElement == Component) 123 | { 124 | return TestProperty; 125 | } 126 | } 127 | } 128 | */ 129 | } 130 | 131 | return nullptr; 132 | }; 133 | 134 | if (AActor* OwnerActor = ComponentInstance->GetOwner()) 135 | { 136 | // First see if the name just works 137 | UClass* OwnerActorClass = OwnerActor->GetClass(); 138 | if (FObjectProperty* TestProperty = FindFProperty(OwnerActorClass, ComponentInstance->GetFName())) 139 | { 140 | if (ComponentInstance->GetClass()->IsChildOf(TestProperty->PropertyClass)) 141 | { 142 | return TestProperty->GetFName(); 143 | } 144 | } 145 | 146 | // Search on CDO 147 | if (FProperty* ReferencingProp = FindPropertyReferencingComponent(ComponentInstance, false)) 148 | { 149 | return ReferencingProp->GetFName(); 150 | } 151 | // Do a limited second search attempt using real Instance 152 | if (!OwnerActor->HasAnyFlags(RF_ClassDefaultObject)) 153 | { 154 | if (FProperty* ReferencingProp = FindPropertyReferencingComponent(ComponentInstance, true)) 155 | { 156 | return ReferencingProp->GetFName(); 157 | } 158 | } 159 | } 160 | 161 | if (UActorComponent* Archetype = Cast(ComponentInstance->GetArchetype())) 162 | { 163 | if (FProperty* ReferencingProp = FindPropertyReferencingComponent(Archetype, false)) 164 | { 165 | return ReferencingProp->GetFName(); 166 | } 167 | } 168 | 169 | return NAME_None; 170 | } 171 | 172 | FName FComponentInfo::GetVariableName() const 173 | { 174 | FName VariableName = NAME_None; 175 | 176 | USCS_Node* SCS_Node = GetSCSNode(); 177 | UActorComponent* ComponentTemplate = GetComponentTemplate(); 178 | 179 | if (IsInstancedComponent() && (SCS_Node == nullptr) && (ComponentTemplate != nullptr)) 180 | { 181 | if (ComponentTemplate->GetOwner()) 182 | { 183 | SCS_Node = FBlueprintComponentReferenceHelper::FindSCSNodeForInstance(ComponentTemplate, ComponentTemplate->GetOwner()->GetClass()); 184 | } 185 | } 186 | 187 | if (SCS_Node) 188 | { 189 | // Use the same variable name as is obtained by the compiler 190 | VariableName = SCS_Node->GetVariableName(); 191 | } 192 | else if (ComponentTemplate) 193 | { 194 | // Try to find the component anchor variable name (first looks for an exact match then scans for any matching variable that points to the archetype in the CDO) 195 | VariableName = FComponentEditorUtils_FindVariableNameGivenComponentInstance(ComponentTemplate); 196 | } 197 | 198 | return VariableName; 199 | } 200 | 201 | FName FComponentInfo::GetObjectName() const 202 | { 203 | if (UActorComponent* ComponentTemplate = GetComponentTemplate()) 204 | { 205 | return ComponentTemplate->GetFName(); 206 | } 207 | return NAME_None; 208 | } 209 | 210 | FText FComponentInfo::GetDisplayText() const 211 | { 212 | FName VariableName = GetVariableName(); 213 | UActorComponent* ComponentTemplate = GetComponentTemplate(); 214 | 215 | UBlueprint* Blueprint = GetBlueprint(); 216 | UClass* VariableOwner = (Blueprint != nullptr) ? Blueprint->SkeletonGeneratedClass : nullptr; 217 | FProperty* VariableProperty = FindFProperty(VariableOwner, VariableName); 218 | 219 | bool bHasValidVarName = (VariableName != NAME_None); 220 | 221 | bool bIsArrayVariable = bHasValidVarName 222 | && (VariableOwner != nullptr) 223 | && VariableProperty && VariableProperty->IsA(); 224 | 225 | FString Value; 226 | 227 | // Only display SCS node variable names in the tree if they have not been autogenerated 228 | if (bHasValidVarName && !bIsArrayVariable) 229 | { 230 | if (IsNativeComponent()) 231 | { 232 | FStringFormatNamedArguments Args; 233 | Args.Add(TEXT("VarName"), VariableProperty && VariableProperty->IsNative() ? VariableProperty->GetDisplayNameText().ToString() : VariableName.ToString()); 234 | Args.Add(TEXT("CompName"), ComponentTemplate->GetName()); 235 | Value = FString::Format(TEXT("{VarName} ({CompName})"), Args); 236 | } 237 | else 238 | { 239 | Value = VariableName.ToString(); 240 | } 241 | } 242 | else if ( ComponentTemplate != nullptr ) 243 | { 244 | Value = ComponentTemplate->GetFName().ToString(); 245 | } 246 | else 247 | { 248 | FString UnnamedString = LOCTEXT("UnnamedToolTip", "Unnamed").ToString(); 249 | FString NativeString = IsNativeComponent() ? LOCTEXT("NativeToolTip", "Native ").ToString() : TEXT(""); 250 | 251 | if (ComponentTemplate != nullptr) 252 | { 253 | Value = FString::Printf(TEXT("[%s %s%s]"), *UnnamedString, *NativeString, *ComponentTemplate->GetClass()->GetName()); 254 | } 255 | else 256 | { 257 | Value = FString::Printf(TEXT("[%s %s]"), *UnnamedString, *NativeString); 258 | } 259 | } 260 | 261 | return FText::FromString(Value); 262 | } 263 | 264 | FText FComponentInfo::GetTooltipText() const 265 | { 266 | FString Value; 267 | if (UClass* Class = GetComponentClass()) 268 | { 269 | Value += FString::Printf(TEXT("Class: %s"), *Class->GetName()); 270 | } 271 | return FText::FromString(Value); 272 | } 273 | 274 | UBlueprint* FComponentInfo::GetBlueprint() const 275 | { 276 | if (const USCS_Node* SCS_Node = GetSCSNode()) 277 | { 278 | if (const USimpleConstructionScript* SCS = SCS_Node->GetSCS()) 279 | { 280 | return SCS->GetBlueprint(); 281 | } 282 | } 283 | else if (const UActorComponent* ActorComponent = GetComponentTemplate()) 284 | { 285 | if (const AActor* Actor = ActorComponent->GetOwner()) 286 | { 287 | return UBlueprint::GetBlueprintFromClass(Actor->GetClass()); 288 | } 289 | } 290 | 291 | return nullptr; 292 | } 293 | 294 | 295 | USCS_Node* FComponentInfo::GetSCSNode() const 296 | { 297 | return nullptr; 298 | } 299 | 300 | bool FComponentInfo::IsEditorOnlyComponent() const 301 | { 302 | UActorComponent* Template = GetComponentTemplate(); 303 | return Template != nullptr && Template->bIsEditorOnly; 304 | } 305 | 306 | EBlueprintComponentReferenceMode FComponentInfo::GetDesiredMode() const 307 | { 308 | return !GetVariableName().IsNone() ? EBlueprintComponentReferenceMode::Property : EBlueprintComponentReferenceMode::Path; 309 | } 310 | 311 | FString FComponentInfo::ToString() const 312 | { 313 | FString FlagsString; 314 | if (IsNativeComponent()) FlagsString += TEXT("Native "); 315 | if (IsInstancedComponent()) FlagsString += TEXT("Instanced "); 316 | if (IsEditorOnlyComponent()) FlagsString += TEXT("Editor "); 317 | 318 | return FString::Printf( 319 | TEXT("Component ID:[%s] V:[%s] P:[%s] F:[%s] %s"), 320 | *GetNodeID().ToString(), *GetVariableName().ToString(), *GetObjectName().ToString(), *FlagsString, *GetDisplayText().ToString() 321 | ); 322 | } 323 | 324 | FComponentInfo_Default::FComponentInfo_Default(USCS_Node* SCSNode, bool bInIsInherited) : SCSNode(SCSNode) 325 | { 326 | Object = SCSNode->ComponentTemplate; 327 | ObjectClass = SCSNode->ComponentClass; 328 | } 329 | 330 | FComponentInfo_Default::FComponentInfo_Default(UActorComponent* Component, bool bInherited) 331 | { 332 | Object = Component; 333 | ObjectClass = Component ? Component->GetClass() : nullptr; 334 | 335 | AActor* Owner = Component->GetOwner(); 336 | if (Owner != nullptr) 337 | { 338 | ensureMsgf(Owner->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject), TEXT("Use a different node class for instanced components")); 339 | } 340 | } 341 | 342 | bool FComponentInfo_Default::IsNativeComponent() const 343 | { 344 | return GetSCSNode() == nullptr && GetComponentTemplate() != nullptr; 345 | } 346 | 347 | USCS_Node* FComponentInfo_Default::GetSCSNode() const 348 | { 349 | return SCSNode.Get(); 350 | } 351 | 352 | FString FComponentInfo_Default::ToString() const 353 | { 354 | return Super::ToString(); 355 | } 356 | 357 | FComponentInfo_Instanced::FComponentInfo_Instanced(AActor* Owner, UActorComponent* Component) 358 | { 359 | InstancedComponentName = Component->GetFName(); 360 | InstancedComponentOwnerPtr = Owner; 361 | Object = Component; 362 | ObjectClass = Component->GetClass(); 363 | } 364 | 365 | FName FComponentInfo_Instanced::GetVariableName() const 366 | { 367 | FName BaseName = Super::GetVariableName(); 368 | //if (BaseName.IsNone()) 369 | //{ // not always correct, fallback to path mode 370 | // return InstancedComponentName; 371 | //} 372 | return BaseName; 373 | } 374 | 375 | FText FComponentInfo_Instanced::GetDisplayText() const 376 | { 377 | return FText::FromName(InstancedComponentName); 378 | } 379 | 380 | FString FComponentInfo_Instanced::ToString() const 381 | { 382 | return Super::ToString(); 383 | } 384 | 385 | FHierarchyInfo::~FHierarchyInfo() 386 | { 387 | //Cleaner.Broadcast(); 388 | } 389 | 390 | FString FHierarchyInfo::ToString() const 391 | { 392 | TStringBuilder<256> Buffer; 393 | Buffer.Appendf(TEXT("Hierarchy of %s (%s)\n"), 394 | *GetNameSafe(GetClassObject()), IsInstance() ? TEXT("Instance") : TEXT("Default")); 395 | for (auto& Node : GetNodes()) 396 | { 397 | Buffer.Appendf(TEXT("%s\n"), *Node->ToString()); 398 | } 399 | return Buffer.ToString(); 400 | } 401 | 402 | void FHierarchyClassInfo::OnCompiled(class UBlueprint*) 403 | { 404 | bDirty = true; 405 | } 406 | 407 | FHierarchyInstanceInfo::FHierarchyInstanceInfo(AActor* Actor): SourceActor(Actor) 408 | { 409 | ensureAlways(::IsValid(Actor)); 410 | SourceClass = Actor->GetClass(); 411 | ClassDisplayText = SourceClass->GetDisplayNameText(); 412 | } 413 | 414 | void FHierarchyInstanceInfo::OnCompiled(class UBlueprint*) 415 | { 416 | bDirty = true; 417 | } 418 | 419 | FHierarchyClassInfo::FHierarchyClassInfo(UClass* Class) : SourceClass(Class) 420 | { 421 | ensureAlways(::IsValid(Class)); 422 | ClassDisplayText = Class->GetDisplayNameText(); 423 | } 424 | 425 | UClass* FBlueprintComponentReferenceHelper::FindClassByName(const FString& ClassName) 426 | { 427 | if (ClassName.IsEmpty()) 428 | return nullptr; 429 | 430 | UClass* Class = UClass::TryFindTypeSlow(ClassName, EFindFirstObjectOptions::EnsureIfAmbiguous); 431 | if (!Class) 432 | { 433 | Class = LoadObject(nullptr, *ClassName); 434 | } 435 | return Class; 436 | } 437 | 438 | bool FBlueprintComponentReferenceHelper::GetHierarchyFromClass(const UClass* InClass, TArray& OutResult) 439 | { 440 | OutResult.Reset(); 441 | 442 | bool bNoErrors = true; 443 | UClass* CurrentClass = const_cast(InClass); 444 | while (CurrentClass) 445 | { 446 | OutResult.Add(CurrentClass); 447 | 448 | if (CurrentClass == AActor::StaticClass()) 449 | break; 450 | 451 | if (UBlueprintGeneratedClass* CurrentBlueprintClass = Cast(CurrentClass)) 452 | { 453 | UBlueprint* BP = UBlueprint::GetBlueprintFromClass(CurrentBlueprintClass); 454 | 455 | if (BP) 456 | { 457 | bNoErrors &= (BP->Status != BS_Error); 458 | } 459 | 460 | // If valid, use stored ParentClass rather than the actual UClass::GetSuperClass(); handles the case when the class has not been recompiled yet after a reparent operation. 461 | if (BP && BP->ParentClass) 462 | { 463 | CurrentClass = Cast(BP->ParentClass); 464 | } 465 | else 466 | { 467 | check(CurrentClass); 468 | CurrentClass = CurrentClass->GetSuperClass(); 469 | } 470 | } 471 | else 472 | { 473 | check(CurrentClass); 474 | CurrentClass = CurrentClass->GetSuperClass(); 475 | } 476 | } 477 | return bNoErrors; 478 | } 479 | 480 | FName FBlueprintComponentReferenceHelper::FindVariableForInstance(const UActorComponent* InstanceComponent, UClass* ClassToSearch) 481 | { 482 | return FComponentEditorUtils::FindVariableNameGivenComponentInstance(InstanceComponent); 483 | } 484 | 485 | USCS_Node* FBlueprintComponentReferenceHelper::FindSCSNodeForInstance(const UActorComponent* InstanceComponent,UClass* ClassToSearch) 486 | { 487 | if ((ClassToSearch != nullptr) && InstanceComponent->IsCreatedByConstructionScript()) 488 | { 489 | for (UClass* TestClass = ClassToSearch; TestClass->ClassGeneratedBy != nullptr; TestClass = TestClass->GetSuperClass()) 490 | { 491 | if (UBlueprint* TestBP = Cast(TestClass->ClassGeneratedBy)) 492 | { 493 | if (TestBP->SimpleConstructionScript != nullptr) 494 | { 495 | if (USCS_Node* Result = TestBP->SimpleConstructionScript->FindSCSNode(InstanceComponent->GetFName())) 496 | { 497 | return Result; 498 | } 499 | } 500 | } 501 | } 502 | } 503 | 504 | return nullptr; 505 | } 506 | 507 | bool FBlueprintComponentReferenceHelper::DoesReferenceMatch(const FBlueprintComponentReference& InRef, const FComponentInfo& Value) 508 | { 509 | switch (InRef.Mode) 510 | { 511 | case EBlueprintComponentReferenceMode::Property: 512 | return Value.GetVariableName() == InRef.Value; 513 | case EBlueprintComponentReferenceMode::Path: 514 | return Value.GetObjectName() == InRef.Value; 515 | default: 516 | return false; 517 | } 518 | } 519 | 520 | TSharedPtr FComponentPickerContext::FindComponent(const FBlueprintComponentReference& InRef, bool bSafeSearch) const 521 | { 522 | if (InRef.IsNull()) 523 | { 524 | return nullptr; 525 | } 526 | 527 | // Search across component hierarchy 528 | for (const auto& ClassDetails : ClassHierarchy) 529 | { 530 | for (const auto& Node : ClassDetails->GetNodes()) 531 | { 532 | if ((FBlueprintComponentReferenceHelper::DoesReferenceMatch(InRef, *Node))) 533 | { 534 | return Node; 535 | } 536 | } 537 | } 538 | 539 | // Dealing with unknown component reference 540 | { 541 | const FString SearchKey = InRef.ToString(); 542 | if (TSharedPtr Unknown = Unknowns.FindRef(SearchKey)) 543 | { 544 | return Unknown; 545 | } 546 | 547 | auto Unknown = MakeShared(); 548 | Unknown->Mode = InRef.GetMode(); 549 | Unknown->Value = InRef.GetValue(); 550 | Unknowns.Add(SearchKey, Unknown); 551 | return Unknown; 552 | } 553 | 554 | return nullptr; 555 | } 556 | 557 | TSharedPtr FComponentPickerContext::FindComponentForVariable(const FName& InName) const 558 | { 559 | return FindComponent(FBlueprintComponentReference(EBlueprintComponentReferenceMode::Property, InName), false); 560 | } 561 | 562 | bool FBlueprintComponentReferenceHelper::IsComponentReferenceProperty(const FProperty* InProperty) 563 | { 564 | bool bDoesMatch = false; 565 | 566 | if (auto AsStruct = CastField(InProperty)) 567 | { 568 | bDoesMatch = IsComponentReferenceType(AsStruct->Struct); 569 | } 570 | else if (auto AsArray = CastField(InProperty)) 571 | { 572 | bDoesMatch = IsComponentReferenceProperty(AsArray->Inner); 573 | } 574 | else if (auto AsSet = CastField(InProperty)) 575 | { 576 | bDoesMatch = IsComponentReferenceProperty(AsSet->ElementProp); 577 | } 578 | else if (auto AsMap = CastField(InProperty)) 579 | { 580 | bDoesMatch = IsComponentReferenceProperty(AsMap->KeyProp) || IsComponentReferenceProperty(AsMap->ValueProp); 581 | } 582 | 583 | return bDoesMatch; 584 | } 585 | 586 | bool FBlueprintComponentReferenceHelper::IsComponentReferenceType(const UStruct* InStruct) 587 | { 588 | return InStruct && InStruct->IsChildOf(FBlueprintComponentReference::StaticStruct()); 589 | } 590 | 591 | TSharedPtr FBlueprintComponentReferenceHelper::CreateChooserContext(AActor* InActor, UClass* InClass, const FString& InLabel) 592 | { 593 | bInitializedAtLeastOnce = true; 594 | 595 | CleanupStaleData(true); 596 | 597 | if (!IsValid(InActor) && !IsValid(InClass)) 598 | { // we called from bad context that has no knowledge of owning class or blueprint 599 | return nullptr; 600 | } 601 | 602 | TSharedRef Ctx = MakeShared(); 603 | Ctx->Label = InLabel; 604 | Ctx->Actor = InActor; 605 | Ctx->Class = InClass; 606 | 607 | ActiveContexts.Emplace(InLabel, Ctx); 608 | 609 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("%s Build for %s of type %s"), *Ctx->Label, *GetNameSafe(InActor), *GetNameSafe(InClass)); 610 | 611 | if (!InActor->IsTemplate()) 612 | { 613 | if (auto InstanceData = GetOrCreateInstanceData(InLabel, InActor)) 614 | { 615 | Ctx->ClassHierarchy.Add(InstanceData); 616 | } 617 | } 618 | 619 | /** 620 | * Record class hierarchy recursively. 621 | */ 622 | { 623 | TArray Classes; 624 | GetHierarchyFromClass(Ctx->Class.Get(), Classes); 625 | 626 | for (UClass* Class : Classes) 627 | { 628 | if (auto ClassData = GetOrCreateClassData(InLabel, Class)) 629 | { 630 | Ctx->ClassHierarchy.Add(ClassData); 631 | } 632 | } 633 | } 634 | 635 | return Ctx; 636 | } 637 | 638 | template 639 | inline void CleanupStaleDataImpl(Map& InMap) 640 | { 641 | for(auto It = InMap.CreateIterator(); It; ++It) 642 | { 643 | if (!It->Value.IsValid() || !It->Value->IsValidInfo()) 644 | { 645 | It.RemoveCurrent(); 646 | continue; 647 | } 648 | 649 | bool bHasGoneBad = false; 650 | for (auto& Ptr : It->Value->Nodes) 651 | { 652 | if (!Ptr.IsValid() || !Ptr->IsValidInfo()) 653 | { 654 | bHasGoneBad = true; 655 | break; 656 | } 657 | } 658 | 659 | if (bHasGoneBad) 660 | { 661 | It.RemoveCurrent(); 662 | break; 663 | } 664 | } 665 | } 666 | 667 | void FBlueprintComponentReferenceHelper::CleanupStaleData(bool bForce) 668 | { 669 | if (!bForce && (FPlatformTime::Seconds() - LastCacheCleanup ) < 0.2f) 670 | { 671 | return; 672 | } 673 | 674 | for (auto It = ActiveContexts.CreateIterator(); It; ++It) 675 | { 676 | if (!It->Value.IsValid()) 677 | { 678 | It.RemoveCurrent(); 679 | continue; 680 | } 681 | } 682 | 683 | if (GBCRCacheEnabled && bInitializedAtLeastOnce) 684 | { 685 | CleanupStaleDataImpl(InstanceCache); 686 | CleanupStaleDataImpl(ClassCache); 687 | } 688 | 689 | LastCacheCleanup = FPlatformTime::Seconds(); 690 | } 691 | 692 | /** 693 | * mark all blueprint related data as dirty and be recreated on next access 694 | */ 695 | void FBlueprintComponentReferenceHelper::MarkBlueprintCacheDirty() 696 | { 697 | if (GBCRCacheEnabled && bInitializedAtLeastOnce) 698 | { 699 | for (auto& Pair : InstanceCache) 700 | { 701 | if (Pair.Value.IsValid() && Pair.Value->IsBlueprint()) 702 | { 703 | Pair.Value->bDirty = true; 704 | } 705 | } 706 | 707 | for (auto& Pair : ClassCache) 708 | { 709 | if (Pair.Value.IsValid() && Pair.Value->IsBlueprint()) 710 | { 711 | Pair.Value->bDirty = true; 712 | } 713 | } 714 | } 715 | } 716 | 717 | 718 | TSharedPtr FBlueprintComponentReferenceHelper::GetOrCreateInstanceData(FString const& InLabel, AActor* InActor) 719 | { 720 | // disabled due to problems tracking level editor actor change in a simple way 721 | constexpr bool bEnableInstanceDataCache = false; 722 | 723 | ensureAlways(!InActor->IsTemplate()); 724 | 725 | TSharedPtr Entry; 726 | 727 | if (GBCRCacheEnabled && bEnableInstanceDataCache) 728 | { 729 | const FInstanceKey EntryKey = MakeInstanceKey(InActor); 730 | 731 | if (auto* FoundExisting = InstanceCache.Find(EntryKey)) 732 | { 733 | Entry = *FoundExisting; 734 | if (!Entry->bDirty) 735 | { 736 | return Entry; 737 | } 738 | } 739 | // Create fresh entry 740 | Entry = InstanceCache.Emplace(EntryKey, MakeShared(InActor)); 741 | } 742 | else 743 | { 744 | Entry = MakeShared(InActor); 745 | } 746 | 747 | check(Entry.IsValid()); 748 | 749 | if (UBlueprintGeneratedClass* BP = Cast(InActor->GetClass())) 750 | { 751 | Entry->bIsBlueprint = true; 752 | 753 | if (GBCRCacheEnabled && bEnableInstanceDataCache) 754 | { // track blueprint for modifications 755 | if (UBlueprint* BPA = Cast(BP->ClassGeneratedBy)) 756 | { 757 | BPA->OnCompiled().AddSP(Entry.ToSharedRef(), &FHierarchyInstanceInfo::OnCompiled); 758 | } 759 | } 760 | 761 | // todo: need find a way to track level actor change 762 | } 763 | 764 | TInlineComponentArray Components; 765 | InActor->GetComponents(Components); 766 | 767 | for (UActorComponent* Object : Components) 768 | { 769 | if (Object->CreationMethod == EComponentCreationMethod::Instance) 770 | { 771 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("%s register INS node %s"), *InLabel, *BuildComponentInfo(Object)); 772 | Entry->Nodes.Add(CreateFromInstance(Object)); 773 | } 774 | } 775 | 776 | return Entry; 777 | } 778 | 779 | TSharedPtr FBlueprintComponentReferenceHelper::GetOrCreateClassData(FString const& InLabel, UClass* InClass) 780 | { 781 | ensureAlways(::IsValid(InClass)); 782 | 783 | TSharedPtr Entry; 784 | 785 | if (GBCRCacheEnabled) 786 | { 787 | const FClassKey EntryKey = MakeClassKey(InClass); 788 | 789 | if (auto* FoundExisting = ClassCache.Find(EntryKey)) 790 | { 791 | Entry = *FoundExisting; 792 | if (!Entry->bDirty) 793 | { 794 | return Entry; 795 | } 796 | } 797 | // Create fresh entry instead of reusing existing one, old delegate regs will be invalid 798 | Entry = ClassCache.Emplace(EntryKey, MakeShared(InClass)); 799 | } 800 | else 801 | { 802 | Entry = MakeShared(InClass); 803 | } 804 | 805 | check(Entry.IsValid()); 806 | 807 | /** 808 | * If we looking a blueprint - skim its construction script for components 809 | */ 810 | if (auto* BPClass = Entry->GetClass()) 811 | { 812 | Entry->bIsBlueprint = true; 813 | 814 | for(USCS_Node* SCSNode : BPClass->SimpleConstructionScript->GetAllNodes()) 815 | { 816 | auto Template = SCSNode->GetActualComponentTemplate(BPClass); 817 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("%s register BPR node %s"), *InLabel, *BuildComponentInfo(Template)); 818 | 819 | Entry->Nodes.Add(CreateFromNode(SCSNode)); 820 | } 821 | 822 | if (GBCRCacheEnabled) 823 | { // track blueprint changes to refresh related information 824 | if (UBlueprint* BPA = Cast(BPClass->ClassGeneratedBy)) 825 | { 826 | BPA->OnCompiled().AddSP(Entry.ToSharedRef(), &FHierarchyClassInfo::OnCompiled); 827 | } 828 | } 829 | } 830 | /** 831 | * If we looking a native class - look in default subobjects 832 | */ 833 | else if (auto* NtClass = Entry->GetClass()) 834 | { 835 | TInlineComponentArray Components; 836 | NtClass->GetDefaultObject()->GetComponents(Components); 837 | 838 | for (UActorComponent* Object : Components) 839 | { 840 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("%s register NAT node %s"), *InLabel, *BuildComponentInfo(Object)); 841 | 842 | Entry->Nodes.Add(CreateFromInstance(Object)); 843 | } 844 | } 845 | 846 | return Entry; 847 | } 848 | 849 | TSharedPtr FBlueprintComponentReferenceHelper::CreateFromNode(USCS_Node* InComponentNode) 850 | { 851 | check(InComponentNode); 852 | return MakeShared(InComponentNode); 853 | } 854 | 855 | TSharedPtr FBlueprintComponentReferenceHelper::CreateFromInstance(UActorComponent* InComponent) 856 | { 857 | check(InComponent); 858 | 859 | AActor* Owner = InComponent->GetOwner(); 860 | if (IsValid(Owner) && !Owner->IsTemplate()) 861 | { 862 | return MakeShared(Owner, InComponent); 863 | } 864 | 865 | return MakeShared(InComponent); 866 | } 867 | 868 | bool FBlueprintComponentReferenceHelper::IsBlueprintProperty(const FProperty* VariableProperty) 869 | { 870 | if(UClass* const VarSourceClass = VariableProperty ? VariableProperty->GetOwner() : nullptr) 871 | { 872 | return (VarSourceClass->ClassGeneratedBy != nullptr); 873 | } 874 | return false; 875 | } 876 | 877 | void FBlueprintComponentReferenceHelper::DebugForceCleanup() 878 | { 879 | CleanupStaleData(true); 880 | } 881 | 882 | static void DumpHierarchy(FHierarchyInfo& InHierarchy) 883 | { 884 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("%s"), *InHierarchy.ToString()); 885 | } 886 | 887 | void FBlueprintComponentReferenceHelper::DebugDumpInstances(const TArray& Args) 888 | { 889 | if (Args.Num() == 0) 890 | { 891 | for (auto& InstanceCacheEntry : InstanceCache) 892 | { 893 | auto& Key = InstanceCacheEntry.Key; 894 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("Instance [%s %s %s]"), 895 | *Key.Get<0>().ToString(), *Key.Get<1>().ToString(), *Key.Get<2>().ToString()); 896 | } 897 | } 898 | else if (Args.Num() == 1) 899 | { 900 | FName Selector = *Args[0]; 901 | for (auto& CacheEntry : InstanceCache) 902 | { 903 | auto& Key = CacheEntry.Key; 904 | if (Key.Get<1>() == Selector || Key.Get<2>() == Selector) 905 | { 906 | FString Dump = CacheEntry.Value->ToString(); 907 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("Instance [%s %s %s]:\n%s"), 908 | *Key.Get<0>().ToString(), *Key.Get<1>().ToString(), *Key.Get<2>().ToString(), *Dump); 909 | 910 | DumpHierarchy(*CacheEntry.Value); 911 | } 912 | } 913 | } 914 | } 915 | 916 | void FBlueprintComponentReferenceHelper::DebugDumpClasses(const TArray& Args) 917 | { 918 | if (Args.Num() == 0) 919 | { 920 | for (auto& CacheEntry : ClassCache) 921 | { 922 | auto& Key = CacheEntry.Key; 923 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("Class [%s %s]"), *Key.Get<0>().ToString(), *Key.Get<1>().ToString()); 924 | } 925 | } 926 | else if (Args.Num() == 1) 927 | { 928 | FName Selector = *Args[0]; 929 | for (auto& CacheEntry : ClassCache) 930 | { 931 | auto& Key = CacheEntry.Key; 932 | if (Key.Get<1>() == Selector) 933 | { 934 | FString Dump = CacheEntry.Value->ToString(); 935 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("Class [%s %s]:\n%s"), *Key.Get<0>().ToString(), *Key.Get<1>().ToString(), *Dump); 936 | 937 | DumpHierarchy(*CacheEntry.Value); 938 | } 939 | } 940 | } 941 | } 942 | 943 | void FBlueprintComponentReferenceHelper::DebugDumpContexts(const TArray Args) 944 | { 945 | if (Args.Num() == 0) 946 | { 947 | for (auto& CacheEntry : ActiveContexts) 948 | { 949 | auto& Key = CacheEntry.Key; 950 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("Context [%s]"), *Key); 951 | } 952 | } 953 | else if (Args.Num() == 1) 954 | { 955 | for (auto& CacheEntry : ActiveContexts) 956 | { 957 | auto& Key = CacheEntry.Key; 958 | if (!Key.Contains(Args[0])) 959 | continue; 960 | 961 | if (CacheEntry.Value.IsValid()) 962 | { 963 | auto Pinned = CacheEntry.Value.Pin(); 964 | 965 | UE_LOG(LogComponentReferenceEditor, Log, TEXT("Context [%s]"), *Key); 966 | 967 | for (const TSharedPtr& ErrorHist : Pinned->ClassHierarchy) 968 | { 969 | DumpHierarchy(*ErrorHist); 970 | } 971 | } 972 | } 973 | } 974 | } 975 | 976 | 977 | #undef LOCTEXT_NAMESPACE 978 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceHelper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BlueprintComponentReference.h" 7 | #include "BlueprintComponentReferenceLibrary.h" 8 | #include "BlueprintComponentReferenceMetadata.h" 9 | #include "Components/ActorComponent.h" 10 | #include "GameFramework/Actor.h" 11 | #include "Engine/World.h" 12 | #include "Engine/Blueprint.h" 13 | #include "Engine/SCS_Node.h" 14 | #include "Templates/TypeHash.h" 15 | #include "Misc/EngineVersionComparison.h" 16 | 17 | #if UE_VERSION_OLDER_THAN(5,4,0) 18 | inline static FName GetFNameSafe(const UObject* InField) 19 | { 20 | if (IsValid(InField)) 21 | { 22 | return InField->GetFName(); 23 | } 24 | return NAME_None; 25 | } 26 | #endif 27 | 28 | 29 | /** 30 | * @see FSCSEditorTreeNodeComponentBase 31 | */ 32 | struct FComponentInfo 33 | { 34 | protected: 35 | TWeakObjectPtr Object; 36 | TWeakObjectPtr ObjectClass; 37 | public: 38 | FComponentInfo() = default; 39 | virtual ~FComponentInfo() = default; 40 | 41 | virtual UActorComponent* GetComponentTemplate() const; 42 | virtual UClass* GetComponentClass() const; 43 | 44 | virtual FName GetNodeID() const; 45 | virtual FName GetVariableName() const; 46 | virtual FName GetObjectName() const; 47 | virtual FText GetDisplayText() const; 48 | virtual FText GetTooltipText() const; 49 | virtual UBlueprint* GetBlueprint() const; 50 | virtual USCS_Node* GetSCSNode() const; 51 | 52 | virtual bool IsUnknown() const { return false; } 53 | virtual bool IsBlueprintComponent() const { return !IsNativeComponent(); } 54 | virtual bool IsNativeComponent() const { return false; } 55 | virtual bool IsInstancedComponent() const { return false; } 56 | virtual bool IsEditorOnlyComponent() const; 57 | virtual EBlueprintComponentReferenceMode GetDesiredMode() const; 58 | 59 | virtual FString ToString() const; 60 | virtual bool IsValidInfo() const { return Object.IsValid() && ObjectClass.IsValid(); } 61 | }; 62 | /** 63 | * @see FSCSEditorTreeNodeComponent 64 | */ 65 | struct FComponentInfo_Default : public FComponentInfo 66 | { 67 | private: 68 | using Super = FComponentInfo; 69 | protected: 70 | TWeakObjectPtr SCSNode; 71 | bool bIsInherited = false; 72 | public: 73 | explicit FComponentInfo_Default(USCS_Node* InSCSNode, bool bInIsInherited = false); 74 | explicit FComponentInfo_Default(UActorComponent* Component, bool bInIsInherited = false); 75 | 76 | virtual bool IsNativeComponent() const override; 77 | virtual USCS_Node* GetSCSNode() const override; 78 | 79 | virtual FString ToString() const override; 80 | virtual bool IsValidInfo() const override { return Super::IsValidInfo() && SCSNode.IsValid(); } 81 | }; 82 | /** 83 | * @see FSCSEditorTreeNodeInstanceAddedComponent 84 | */ 85 | struct FComponentInfo_Instanced : public FComponentInfo 86 | { 87 | private: 88 | using Super = FComponentInfo; 89 | protected: 90 | FName InstancedComponentName; 91 | TWeakObjectPtr InstancedComponentOwnerPtr; 92 | public: 93 | explicit FComponentInfo_Instanced(AActor* Owner, UActorComponent* Component); 94 | virtual bool IsInstancedComponent() const override { return true; } 95 | virtual FName GetVariableName() const override; 96 | virtual FText GetDisplayText() const override; 97 | virtual FName GetObjectName() const override { return InstancedComponentName; } 98 | 99 | virtual FString ToString() const override; 100 | virtual bool IsValidInfo() const override { return Super::IsValidInfo() && InstancedComponentOwnerPtr.IsValid(); } 101 | }; 102 | 103 | struct FComponentInfo_Unknown : public FComponentInfo 104 | { 105 | EBlueprintComponentReferenceMode Mode; 106 | FName Value; 107 | 108 | virtual FText GetDisplayText() const override { return FText::FromName(Value); } 109 | virtual UClass* GetComponentClass() const override { return UActorComponent::StaticClass(); } 110 | virtual UActorComponent* GetComponentTemplate() const override { return nullptr; } 111 | virtual FText GetTooltipText() const override { return INVTEXT("Failed to locate component information"); } 112 | virtual bool IsUnknown() const override { return true; } 113 | virtual bool IsBlueprintComponent() const override { return true; } 114 | virtual bool IsNativeComponent() const override { return true; } 115 | virtual bool IsInstancedComponent() const override { return true; } 116 | virtual EBlueprintComponentReferenceMode GetDesiredMode() const override { return Mode; } 117 | virtual FName GetVariableName() const override { return Mode == EBlueprintComponentReferenceMode::Property ? Value : NAME_None; } 118 | virtual FName GetObjectName() const override { return Mode == EBlueprintComponentReferenceMode::Path ? Value : NAME_None; } 119 | }; 120 | 121 | struct FHierarchyInfo 122 | { 123 | TArray> Nodes; 124 | bool bDirty = false; 125 | //TMulticastDelegate Cleaner; 126 | 127 | virtual ~FHierarchyInfo(); 128 | // Group items 129 | virtual const TArray>& GetNodes() const { return Nodes; } 130 | // Group related class object 131 | virtual UClass* GetClassObject() const = 0; 132 | // Group display name 133 | virtual FText GetDisplayText() const = 0; 134 | // Is category considered to be a blueprint 135 | virtual bool IsBlueprint() const { return false; } 136 | // Is category considered to be an instance-only 137 | virtual bool IsInstance() const { return false; } 138 | // 139 | virtual FString ToString() const; 140 | // 141 | virtual bool IsValidInfo() const = 0; 142 | 143 | template 144 | T* GetClass() const { return Cast(GetClassObject()); } 145 | }; 146 | 147 | struct FHierarchyClassInfo : public FHierarchyInfo 148 | { 149 | private: 150 | using Super = FHierarchyInfo; 151 | public: 152 | FHierarchyClassInfo(UClass* Class); 153 | virtual ~FHierarchyClassInfo() = default; 154 | 155 | TWeakObjectPtr SourceClass; 156 | FText ClassDisplayText; 157 | bool bIsBlueprint = false; 158 | 159 | virtual UClass* GetClassObject() const override { return SourceClass.Get(); } 160 | virtual FText GetDisplayText() const override { return ClassDisplayText; } 161 | virtual bool IsBlueprint() const override { return bIsBlueprint; } 162 | virtual bool IsValidInfo() const override { return SourceClass.IsValid(); } 163 | 164 | void OnCompiled(class UBlueprint*); 165 | }; 166 | 167 | struct FHierarchyInstanceInfo : public FHierarchyInfo 168 | { 169 | private: 170 | using Super = FHierarchyInfo; 171 | public: 172 | TWeakObjectPtr SourceActor; 173 | TWeakObjectPtr SourceClass; 174 | FText ClassDisplayText; 175 | bool bIsBlueprint = false; 176 | 177 | FHierarchyInstanceInfo(AActor* Actor); 178 | 179 | virtual bool IsInstance() const override { return true; } 180 | virtual UClass* GetClassObject() const override { return SourceClass.Get(); } 181 | virtual FText GetDisplayText() const override { return INVTEXT("Instance"); } 182 | virtual bool IsBlueprint() const override { return bIsBlueprint; } 183 | virtual bool IsValidInfo() const override { return SourceActor.IsValid() && SourceClass.IsValid(); } 184 | 185 | void OnCompiled(class UBlueprint*); 186 | }; 187 | 188 | struct FComponentPickerContext 189 | { 190 | FString Label; 191 | 192 | TWeakObjectPtr Actor; 193 | TWeakObjectPtr Class; 194 | TArray> ClassHierarchy; 195 | 196 | mutable TMap> Unknowns; 197 | 198 | AActor* GetActor() const { return Actor.Get(); } 199 | UClass* GetClass() const { return Class.Get(); } 200 | 201 | /** 202 | * Lookup for component information 203 | * @param InRef Component reference to resolve 204 | * @param bSafeSearch Should return instance of Unknown if no information available 205 | * @return Component information 206 | */ 207 | TSharedPtr FindComponent(const FBlueprintComponentReference& InRef, bool bSafeSearch) const; 208 | TSharedPtr FindComponentForVariable(const FName& InName) const; 209 | 210 | 211 | }; 212 | 213 | /** 214 | * BCR customization manager. 215 | * 216 | * Holds internal data about hierarchies and components. 217 | * 218 | * maybe merge back to module class? 219 | */ 220 | class FBlueprintComponentReferenceHelper : public TSharedFromThis 221 | { 222 | public: 223 | using FInstanceKey = TTuple; 224 | using FClassKey = TTuple; 225 | 226 | /** 227 | * Test if property is supported by BCR customization 228 | */ 229 | static bool IsComponentReferenceProperty(const FProperty* InProperty); 230 | 231 | /** 232 | * Test if type is a BCR type 233 | */ 234 | static bool IsComponentReferenceType(const UStruct* InStruct); 235 | 236 | /** 237 | * Get or create component chooser data source for specific input parameters 238 | * 239 | * @param InActor Input actor 240 | * @param InClass Input class 241 | * @param InLabel Debug marker 242 | * @return Context instance 243 | */ 244 | TSharedPtr CreateChooserContext(AActor* InActor, UClass* InClass, const FString& InLabel); 245 | 246 | /** 247 | * Cleanup stale hierarchy data 248 | */ 249 | void CleanupStaleData(bool bForce = false); 250 | 251 | /** 252 | * 253 | */ 254 | void MarkBlueprintCacheDirty(); 255 | 256 | /** 257 | * Collect components info specific to live actor instance 258 | * 259 | * @param InLabel Actor label, debug purpose only 260 | * @param InActor Actor instance to collect information from 261 | * @return 262 | */ 263 | TSharedPtr GetOrCreateInstanceData(FString const& InLabel, AActor* InActor); 264 | 265 | /** 266 | * Collect components info specific to class 267 | * 268 | * @param InLabel Class label, debug purpose only 269 | * @param InClass Class instance to collect information from 270 | * @return 271 | */ 272 | TSharedPtr GetOrCreateClassData(FString const& InLabel, UClass* InClass); 273 | 274 | static TSharedPtr CreateFromNode(USCS_Node* InComponentNode); 275 | static TSharedPtr CreateFromInstance(UActorComponent* Component); 276 | 277 | /** IS it a blueprint property or not */ 278 | static bool IsBlueprintProperty(const FProperty* VariableProperty); 279 | 280 | /** */ 281 | static UClass* FindClassByName(const FString& ClassName); 282 | 283 | /** */ 284 | static bool GetHierarchyFromClass(const UClass* InClass, TArray& OutResult); 285 | 286 | // Tries to find a Variable that likely holding instance component. 287 | static FName FindVariableForInstance(const UActorComponent* InstanceComponent, UClass* ClassToSearch); 288 | 289 | // Tries to find a SCS node that was likely responsible for creating the specified instance component. Note: This is not always possible to do! 290 | static USCS_Node* FindSCSNodeForInstance(const UActorComponent* InstanceComponent, UClass* ClassToSearch); 291 | 292 | static bool DoesReferenceMatch(const FBlueprintComponentReference& InRef, const FComponentInfo& Value); 293 | 294 | void DebugDumpInstances(const TArray& Args); 295 | void DebugDumpClasses(const TArray& Args); 296 | void DebugDumpContexts(const TArray Array); 297 | void DebugForceCleanup(); 298 | 299 | private: 300 | float LastCacheCleanup = 0; 301 | bool bInitializedAtLeastOnce = false; 302 | 303 | TMap> ActiveContexts; 304 | 305 | TMap> InstanceCache; 306 | 307 | TMap> ClassCache; 308 | }; 309 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceMetadata.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BlueprintComponentReferenceMetadata.h" 4 | #include "BlueprintComponentReferenceHelper.h" 5 | #include "BlueprintComponentReferenceEditor.h" 6 | #include "Engine/Blueprint.h" 7 | #include "Templates/TypeHash.h" 8 | #include "Misc/EngineVersionComparison.h" 9 | 10 | #include "UObject/UObjectIterator.h" 11 | 12 | const FName FCRMetadataKey::ActorClass = "ActorClass"; 13 | const FName FCRMetadataKey::AllowedClasses = "AllowedClasses"; 14 | const FName FCRMetadataKey::DisallowedClasses = "DisallowedClasses"; 15 | const FName FCRMetadataKey::NoClear = "NoClear"; 16 | const FName FCRMetadataKey::NoNavigate = "NoNavigate"; 17 | const FName FCRMetadataKey::NoPicker = "NoPicker"; 18 | const FName FCRMetadataKey::ShowBlueprint = "ShowBlueprint"; 19 | const FName FCRMetadataKey::ShowNative = "ShowNative"; 20 | const FName FCRMetadataKey::ShowInstanced = "ShowInstanced"; 21 | const FName FCRMetadataKey::ShowHidden = "ShowHidden"; 22 | const FName FCRMetadataKey::ShowEditor = "ShowEditor"; 23 | 24 | void FBlueprintComponentReferenceMetadata::ResetSettings() 25 | { 26 | static const FBlueprintComponentReferenceMetadata DefaultValues; 27 | *this = DefaultValues; 28 | } 29 | 30 | void FBlueprintComponentReferenceMetadata::LoadSettingsFromProperty(const FProperty* InProp) 31 | { 32 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("LoadSettingsFromProperty(%s)"), *InProp->GetFName().ToString()); 33 | 34 | static const FBlueprintComponentReferenceMetadata DefaultValues; 35 | 36 | // picker 37 | bUsePicker = !FMetadataMarshaller::HasMetaDataValue(InProp, FCRMetadataKey::NoPicker); 38 | // actions 39 | bUseNavigate = !FMetadataMarshaller::HasMetaDataValue(InProp, FCRMetadataKey::NoNavigate); 40 | bUseClear = !(InProp->PropertyFlags & CPF_NoClear) && !FMetadataMarshaller::HasMetaDataValue(InProp, FCRMetadataKey::NoClear); 41 | // filters 42 | bShowNative = FMetadataMarshaller::GetBoolMetaDataValue(InProp, FCRMetadataKey::ShowNative).Get(DefaultValues.bShowNative); 43 | bShowBlueprint = FMetadataMarshaller::GetBoolMetaDataValue(InProp, FCRMetadataKey::ShowBlueprint).Get(DefaultValues.bShowBlueprint); 44 | bShowInstanced = FMetadataMarshaller::GetBoolMetaDataValue(InProp, FCRMetadataKey::ShowInstanced).Get(DefaultValues.bShowInstanced); 45 | bShowHidden = FMetadataMarshaller::GetBoolMetaDataValue(InProp, FCRMetadataKey::ShowHidden).Get(DefaultValues.bShowHidden); 46 | bShowEditor = FMetadataMarshaller::GetBoolMetaDataValue(InProp, FCRMetadataKey::ShowEditor).Get( DefaultValues.bShowEditor); 47 | 48 | FMetadataMarshaller::GetClassMetadata(InProp, FCRMetadataKey::ActorClass, [this](UClass* InClass) 49 | { 50 | ActorClass = InClass; 51 | }); 52 | 53 | FMetadataMarshaller::GetClassListMetadata(InProp, FCRMetadataKey::AllowedClasses, [this](UClass* InClass) 54 | { 55 | AllowedClasses.AddUnique(InClass); 56 | }); 57 | 58 | FMetadataMarshaller::GetClassListMetadata(InProp, FCRMetadataKey::DisallowedClasses, [this](UClass* InClass) 59 | { 60 | DisallowedClasses.AddUnique(InClass); 61 | }); 62 | } 63 | 64 | void FBlueprintComponentReferenceMetadata::ApplySettingsToProperty(UBlueprint* InBlueprint, FProperty* InProperty, const FName& InChanged) 65 | { 66 | UE_LOG(LogComponentReferenceEditor, Verbose, TEXT("ApplySettingsToProperty(%s)"), *InProperty->GetName()); 67 | 68 | auto BoolToString = [](bool b) -> TOptional 69 | { 70 | return TOptional(b ? TEXT("True") : TEXT("False")); 71 | }; 72 | 73 | auto BoolToFlag = [](bool b) -> TOptional 74 | { 75 | return b ? TOptional(TEXT("")): TOptional(); 76 | }; 77 | 78 | auto ClassToString = [](const UClass* InClass) -> TOptional 79 | { 80 | return InClass ? InClass->GetClassPathName().ToString() : TOptional(); 81 | }; 82 | 83 | auto ArrayToString = [](const TArray>& InArray) 84 | { 85 | TArray> Paths; 86 | for (const TSubclassOf& Class : InArray) 87 | { 88 | if (IsValid(Class)) 89 | { 90 | Paths.AddUnique(Class->GetClassPathName().ToString()); 91 | } 92 | } 93 | return FString::Join(Paths, TEXT(",")); 94 | }; 95 | 96 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bUsePicker)) 97 | { 98 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::NoPicker, BoolToFlag(!bUsePicker)); 99 | } 100 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bUseNavigate)) 101 | { 102 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::NoNavigate, BoolToFlag(!bUseNavigate)); 103 | } 104 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bUseClear)) 105 | { 106 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::NoClear, BoolToFlag(!bUseClear)); 107 | } 108 | 109 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bShowNative)) 110 | { 111 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::ShowNative, BoolToString(bShowNative)); 112 | } 113 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bShowBlueprint)) 114 | { 115 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::ShowBlueprint, BoolToString(bShowBlueprint)); 116 | } 117 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bShowInstanced)) 118 | { 119 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::ShowInstanced, BoolToString(bShowInstanced)); 120 | } 121 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bShowHidden)) 122 | { 123 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::ShowHidden, BoolToString(bShowHidden)); 124 | } 125 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, bShowEditor)) 126 | { 127 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::ShowEditor, BoolToString(bShowEditor)); 128 | } 129 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, AllowedClasses)) 130 | { 131 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::AllowedClasses, ArrayToString(AllowedClasses)); 132 | } 133 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, DisallowedClasses)) 134 | { 135 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::DisallowedClasses, ArrayToString(DisallowedClasses)); 136 | } 137 | if (InChanged.IsNone() || InChanged == GET_MEMBER_NAME_CHECKED(FBlueprintComponentReferenceMetadata, ActorClass)) 138 | { 139 | FMetadataMarshaller::SetMetaDataValue(InBlueprint, InProperty, FCRMetadataKey::ActorClass, ClassToString(ActorClass.Get())); 140 | } 141 | } 142 | 143 | bool FMetadataMarshaller::HasMetaDataValue(const FProperty* Property, const FName& InName) 144 | { 145 | return Property->HasMetaData(InName); 146 | } 147 | 148 | void FMetadataMarshaller::SetMetaDataValue(UBlueprint* InBlueprint, FProperty* InProperty, const FName& InName, TOptional InValue) 149 | { 150 | check(InProperty); 151 | 152 | if (::IsValid(InBlueprint)) 153 | { 154 | for (FBPVariableDescription& VariableDescription : InBlueprint->NewVariables) 155 | { 156 | if (VariableDescription.VarName == InProperty->GetFName()) 157 | { 158 | if (InValue.IsSet()) 159 | { 160 | InProperty->SetMetaData(InName, *InValue.GetValue()); 161 | VariableDescription.SetMetaData(InName, InValue.GetValue()); 162 | } 163 | else 164 | { 165 | InProperty->RemoveMetaData(InName); 166 | VariableDescription.RemoveMetaData(InName); 167 | } 168 | 169 | InBlueprint->Modify(); 170 | break; 171 | } 172 | } 173 | } 174 | else 175 | { 176 | if (InValue.IsSet()) 177 | { 178 | InProperty->SetMetaData(InName, *InValue.GetValue()); 179 | } 180 | else 181 | { 182 | InProperty->RemoveMetaData(InName); 183 | } 184 | } 185 | } 186 | 187 | TOptional FMetadataMarshaller::GetBoolMetaDataValue(const FProperty* Property, const FName& InName) 188 | { 189 | if (Property->FindMetaData(InName) != nullptr) 190 | { 191 | bool bResult = true; 192 | 193 | const FString& ValueString = Property->GetMetaData(InName); 194 | if (!ValueString.IsEmpty()) 195 | { 196 | if (ValueString.Equals(TEXT("true"), ESearchCase::IgnoreCase)) 197 | { 198 | bResult = true; 199 | } 200 | else if (ValueString.Equals(TEXT("false"), ESearchCase::IgnoreCase)) 201 | { 202 | bResult = false; 203 | } 204 | } 205 | 206 | return TOptional(bResult); 207 | } 208 | 209 | return TOptional(); 210 | } 211 | 212 | void FMetadataMarshaller::GetClassMetadata(const FProperty* Property, const FName& InName, const TFunctionRef& Func) 213 | { 214 | const FString& ClassName = Property->GetMetaData(InName); 215 | if (ClassName.IsEmpty()) 216 | { 217 | return; 218 | } 219 | 220 | if (UClass* Class = FBlueprintComponentReferenceHelper::FindClassByName(ClassName)) 221 | { 222 | Func(Class); 223 | } 224 | } 225 | 226 | void FMetadataMarshaller::GetClassListMetadata(const FProperty* Property, const FName& InName, const TFunctionRef& Func) 227 | { 228 | const FString& MetaDataString = Property->GetMetaData(InName); 229 | if (MetaDataString.IsEmpty()) 230 | { 231 | return; 232 | } 233 | 234 | TArray ClassFilterNames; 235 | MetaDataString.ParseIntoArrayWS(ClassFilterNames, TEXT(","), true); 236 | 237 | for (const FString& ClassName : ClassFilterNames) 238 | { 239 | if (UClass* Class = FBlueprintComponentReferenceHelper::FindClassByName(ClassName)) 240 | { 241 | if (Class->HasAnyClassFlags(CLASS_Interface) || Class->IsChildOf(UActorComponent::StaticClass())) 242 | { 243 | Func(Class); 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceMetadata.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "UObject/SoftObjectPtr.h" 6 | #include "Templates/SubclassOf.h" 7 | #include "Components/ActorComponent.h" 8 | 9 | #include "BlueprintComponentReferenceMetadata.generated.h" 10 | 11 | struct FCRMetadataKey 12 | { 13 | // 14 | static const FName ActorClass; 15 | // basic 16 | static const FName AllowedClasses; 17 | static const FName DisallowedClasses; 18 | // display flags 19 | static const FName NoClear; 20 | static const FName NoNavigate; 21 | static const FName NoPicker; 22 | // filter flags 23 | static const FName ShowBlueprint; 24 | static const FName ShowNative; 25 | static const FName ShowInstanced; 26 | static const FName ShowHidden; 27 | static const FName ShowEditor; 28 | }; 29 | 30 | /** 31 | * Internal struct for blueprint property configuration and view settings 32 | */ 33 | USTRUCT() 34 | struct BLUEPRINTCOMPONENTREFERENCEEDITOR_API FBlueprintComponentReferenceMetadata 35 | { 36 | GENERATED_BODY() 37 | public: 38 | /** Allow to use Picker feature */ 39 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="NoPicker", MDHandler="InverseBool")) 40 | bool bUsePicker = true; 41 | /** Allow to use Navigate/Browse feature */ 42 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="NoNavigate", MDHandler="InverseBool")) 43 | bool bUseNavigate = true; 44 | /** Allow reference to be reset */ 45 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="NoClear", MDHandler="InverseBool")) 46 | bool bUseClear = true; 47 | 48 | /** 49 | * Enforces specific actor class to collect components from, usually used when automatic discovery is not possible. 50 | * 51 | * Important note: prefer native actor classes over blueprints to avoid loading unnesessary assets 52 | */ 53 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="ActorClass", MDHandler="Class", AllowAbstract=true, NoBrowse, NoCreate, DisallowCreateNew)) 54 | TSoftClassPtr ActorClass; 55 | 56 | /** Allow to pick native components */ 57 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="ShowNative", MDHandler="Bool")) 58 | bool bShowNative = true; 59 | /** Allow to pick blueprint components */ 60 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="ShowBlueprint", MDHandler="Bool")) 61 | bool bShowBlueprint = true; 62 | /** Allow to pick instanced components */ 63 | UPROPERTY(EditAnywhere, Category=Metadata, meta=(MDSpecifier="ShowInstanced", MDHandler="Bool")) 64 | bool bShowInstanced = false; 65 | /** Allow to pick path-only/hidden components */ 66 | UPROPERTY(EditAnywhere, Category=Metadata, DisplayName="Show Hidden", meta=(MDSpecifier="ShowHidden", MDHandler="Bool")) 67 | bool bShowHidden = false; 68 | /** Allow to pick editor-only components */ 69 | UPROPERTY(EditAnywhere, Category=Metadata, DisplayName="Show Editor", meta=(MDSpecifier="ShowEditor", MDHandler="Bool")) 70 | bool bShowEditor = true; 71 | 72 | /** 73 | * ActorComponent classes or interfaces that can be referenced by this property 74 | * 75 | * Important note: prefer native actor classes over blueprints to avoid loading unnesessary assets 76 | */ 77 | UPROPERTY(EditAnywhere, DisplayName="Allowed Classes", Category=Metadata, NoClear, meta=(MDSpecifier="AllowedClasses", MDHandler="ClassList", DisplayThumbnail=false, NoElementDuplicate, AllowAbstract=true, NoBrowse, NoCreate, DisallowCreateNew)) 78 | TArray> AllowedClasses; 79 | /** 80 | * ActorComponent classes or interfaces that can NOT be referenced by this property 81 | * 82 | * Important note: prefer native actor classes over blueprints to avoid loading unnesessary assets 83 | */ 84 | UPROPERTY(EditAnywhere, DisplayName="Disallowed Classes", Category=Metadata, NoClear, meta=(MDSpecifier="DisallowedClasses", MDHandler="ClassList", DisplayThumbnail=false, NoElementDuplicate, AllowAbstract=true, NoBrowse, NoCreate, DisallowCreateNew)) 85 | TArray> DisallowedClasses; 86 | 87 | public: 88 | virtual ~FBlueprintComponentReferenceMetadata() = default; 89 | virtual void ResetSettings(); 90 | virtual void LoadSettingsFromProperty(const FProperty* InProp); 91 | virtual void ApplySettingsToProperty(UBlueprint* InBlueprint, FProperty* InProperty, const FName& InChanged); 92 | }; 93 | 94 | class UBlueprint; 95 | 96 | /** 97 | * An utility class that converts a typed struct container into property metadata and vise-versa and other experiments 98 | */ 99 | struct BLUEPRINTCOMPONENTREFERENCEEDITOR_API FMetadataMarshaller 100 | { 101 | static bool HasMetaDataValue(const FProperty* Property, const FName& InName); 102 | 103 | static void SetMetaDataValue(UBlueprint* Blueprint, FProperty* Property, const FName& InName, TOptional InValue); 104 | 105 | static TOptional GetBoolMetaDataValue(const FProperty* Property, const FName& InName); 106 | 107 | static void GetClassMetadata(const FProperty* Property, const FName& InName, const TFunctionRef& Func); 108 | 109 | static void GetClassListMetadata(const FProperty* Property, const FName& InName, const TFunctionRef& Func); 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceVarCustomization.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BlueprintComponentReferenceVarCustomization.h" 4 | 5 | #include "BlueprintComponentReferenceCustomization.h" 6 | #include "BlueprintComponentReferenceHelper.h" 7 | #include "BlueprintComponentReferenceEditor.h" 8 | #include "BlueprintEditorModule.h" 9 | #include "DetailCategoryBuilder.h" 10 | #include "DetailLayoutBuilder.h" 11 | #include "Kismet2/BlueprintEditorUtils.h" 12 | #include "ScopedTransaction.h" 13 | 14 | FBlueprintComponentReferenceVarCustomization::FBlueprintComponentReferenceVarCustomization(TSharedPtr InBlueprintEditor, TWeakObjectPtr InBlueprintPtr) 15 | { 16 | BlueprintEditorPtr = InBlueprintEditor; 17 | BlueprintPtr = InBlueprintPtr; 18 | } 19 | 20 | TSharedPtr FBlueprintComponentReferenceVarCustomization::MakeInstance(TSharedPtr BlueprintEditor) 21 | { 22 | const TArray* Objects = (BlueprintEditor.IsValid() ? BlueprintEditor->GetObjectsCurrentlyBeingEdited() : nullptr); 23 | if (Objects) 24 | { 25 | TOptional FinalBlueprint; 26 | for (UObject* Object : *Objects) 27 | { 28 | UBlueprint* Blueprint = Cast(Object); 29 | if (Blueprint == nullptr) 30 | { 31 | return nullptr; 32 | } 33 | if (FinalBlueprint.IsSet() && FinalBlueprint.GetValue() != Blueprint) 34 | { 35 | return nullptr; 36 | } 37 | FinalBlueprint = Blueprint; 38 | } 39 | 40 | if (FinalBlueprint.IsSet()) 41 | { 42 | return MakeShared(BlueprintEditor, MakeWeakObjectPtr(FinalBlueprint.GetValue())); 43 | } 44 | } 45 | 46 | return nullptr; 47 | } 48 | 49 | TSharedPtr> FBlueprintComponentReferenceVarCustomization::CreateContainer() const 50 | { 51 | return MakeShared>(MakeStructOnScope()); 52 | } 53 | 54 | void FBlueprintComponentReferenceVarCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) 55 | { 56 | ScopedSettings.Reset(); 57 | PropertiesBeingCustomized.Reset(); 58 | 59 | UBlueprint* LocalBlueprint = BlueprintPtr.Get(); 60 | if (!IsValid(LocalBlueprint)) 61 | return; 62 | 63 | TArray> ObjectsBeingCustomized; 64 | DetailLayout.GetObjectsBeingCustomized(ObjectsBeingCustomized); 65 | for (TWeakObjectPtr& Obj : ObjectsBeingCustomized) 66 | { 67 | UPropertyWrapper* PropertyWrapper = Cast(Obj.Get()); 68 | FProperty* PropertyBeingCustomized = PropertyWrapper ? PropertyWrapper->GetProperty() : nullptr; 69 | if (!PropertyBeingCustomized) 70 | continue; 71 | if (!FBlueprintEditorUtils::IsVariableCreatedByBlueprint(LocalBlueprint, PropertyBeingCustomized)) 72 | continue; 73 | 74 | if (FBlueprintComponentReferenceHelper::IsComponentReferenceProperty(PropertyBeingCustomized)) 75 | { 76 | PropertiesBeingCustomized.Emplace(PropertyBeingCustomized); 77 | } 78 | } 79 | 80 | if (PropertiesBeingCustomized.Num() != 1) 81 | { 82 | return; 83 | } 84 | 85 | ScopedSettings = CreateContainer(); 86 | ScopedSettings->Get()->LoadSettingsFromProperty(PropertiesBeingCustomized[0].Get()); 87 | 88 | { 89 | // Put custom category above `Default Value` 90 | int32 SortOrder = DetailLayout.EditCategory("Variable").GetSortOrder(); 91 | DetailLayout.EditCategory(GetCategoryName()).SetSortOrder(++SortOrder); 92 | DetailLayout.EditCategory("DefaultValue").SetSortOrder(++SortOrder); 93 | } 94 | 95 | { 96 | auto& Builder = DetailLayout.EditCategory(GetCategoryName()); 97 | Builder.InitiallyCollapsed(false); 98 | 99 | for (TFieldIterator It(ScopedSettings->GetStruct(), EFieldIterationFlags::Default); It; ++It) 100 | { 101 | if (It->HasAnyPropertyFlags(CPF_Deprecated|CPF_Transient)) 102 | continue; 103 | 104 | FSimpleDelegate ChangeHandler = FSimpleDelegate::CreateSP(this, &FBlueprintComponentReferenceVarCustomization::OnContainerPropertyChanged, It->GetFName()); 105 | 106 | FAddPropertyParams Params; 107 | IDetailPropertyRow* PropertyRow = Builder.AddExternalStructureProperty(ScopedSettings, It->GetFName(), EPropertyLocation::Default, Params); 108 | PropertyRow->ShouldAutoExpand(true); 109 | 110 | TSharedPtr PropertyHandle = PropertyRow->GetPropertyHandle(); 111 | PropertyHandle->SetOnPropertyValueChanged(ChangeHandler); 112 | PropertyHandle->SetOnChildPropertyValueChanged(ChangeHandler); 113 | } 114 | } 115 | } 116 | 117 | void FBlueprintComponentReferenceVarCustomization::OnContainerPropertyChanged(FName InName) 118 | { 119 | FScopedTransaction Transaction(INVTEXT("ApplySettingsToProperty")); 120 | 121 | FMetadataContainer& Settings = *ScopedSettings->Get(); 122 | 123 | for (const TWeakFieldPtr& Property : PropertiesBeingCustomized) 124 | { 125 | if (FProperty* Local = Property.Get()) 126 | { 127 | Settings.ApplySettingsToProperty(BlueprintPtr.Get(), Local, InName); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/BlueprintComponentReferenceVarCustomization.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "BlueprintComponentReferenceLibrary.h" 6 | #include "BlueprintComponentReferenceMetadata.h" 7 | #include "IDetailCustomization.h" 8 | #include "IDetailCustomNodeBuilder.h" 9 | 10 | class IBlueprintEditor; 11 | class UBlueprint; 12 | 13 | /** 14 | * 15 | */ 16 | class FBlueprintComponentReferenceVarCustomization : public IDetailCustomization 17 | { 18 | using FMetadataContainer = FBlueprintComponentReferenceMetadata; 19 | public: 20 | FBlueprintComponentReferenceVarCustomization( 21 | TSharedPtr InBlueprintEditor, 22 | TWeakObjectPtr InBlueprintPtr 23 | ); 24 | 25 | static TSharedPtr MakeInstance(TSharedPtr BlueprintEditor); 26 | protected: 27 | virtual FName GetCategoryName() const { return TEXT("ComponentReferenceMetadata"); } 28 | virtual TSharedPtr> CreateContainer() const; 29 | 30 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override; 31 | 32 | virtual void OnContainerPropertyChanged(FName InName); 33 | 34 | private: 35 | /** The blueprint editor instance */ 36 | TSharedPtr BlueprintEditorPtr; 37 | 38 | /** The blueprint we are editing */ 39 | TWeakObjectPtr BlueprintPtr; 40 | 41 | /** The property we are editing */ 42 | TArray> PropertiesBeingCustomized; 43 | 44 | /** Object holding aggregate settins to be applied to properties */ 45 | TSharedPtr> ScopedSettings; 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/K2Node_FindComponentInMap.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "K2Node_FindComponentInMap.h" 4 | 5 | #include "BlueprintActionDatabaseRegistrar.h" 6 | #include "BlueprintComponentReferenceLibrary.h" 7 | #include "BlueprintNodeSpawner.h" 8 | #include "EdGraphSchema_K2.h" 9 | #include "Kismet2/WildcardNodeUtils.h" 10 | 11 | UK2Node_FindComponentInMap::UK2Node_FindComponentInMap() 12 | { 13 | FunctionReference.SetExternalMember( 14 | GET_FUNCTION_NAME_CHECKED(UBlueprintComponentReferenceLibrary, Map_FindComponent), 15 | UBlueprintComponentReferenceLibrary::StaticClass() 16 | ); 17 | } 18 | 19 | FText UK2Node_FindComponentInMap::GetMenuCategory() const 20 | { 21 | // return INVTEXT("Utilities|ComponentReference"); 22 | return Super::GetMenuCategory(); 23 | } 24 | 25 | void UK2Node_FindComponentInMap::AllocateDefaultPins() 26 | { 27 | Super::AllocateDefaultPins(); 28 | 29 | ConformPinTypes(); 30 | } 31 | 32 | void UK2Node_FindComponentInMap::NotifyPinConnectionListChanged(UEdGraphPin* Pin) 33 | { 34 | Super::NotifyPinConnectionListChanged(Pin); 35 | 36 | ConformPinTypes(); 37 | } 38 | 39 | void UK2Node_FindComponentInMap::PostReconstructNode() 40 | { 41 | Super::PostReconstructNode(); 42 | 43 | ConformPinTypes(); 44 | } 45 | // @see UK2node_CAllFunction::ConformContainerPins 46 | void UK2Node_FindComponentInMap::ConformPinTypes() 47 | { 48 | const UEdGraphSchema_K2* Schema = CastChecked(GetSchema()); 49 | 50 | const auto TryReadTypeToPropagate = [](UEdGraphPin* Pin, bool& bOutPropagated, FEdGraphTerminalType& TypeToPropagete) 51 | { 52 | if (Pin && !bOutPropagated) 53 | { 54 | if (Pin->HasAnyConnections() || !Pin->DoesDefaultValueMatchAutogenerated() ) 55 | { 56 | FEdGraphTerminalType TypeToPotentiallyPropagate; 57 | if (Pin->LinkedTo.Num() != 0) 58 | { 59 | TypeToPotentiallyPropagate = Pin->LinkedTo[0]->GetPrimaryTerminalType(); 60 | } 61 | else 62 | { 63 | TypeToPotentiallyPropagate = Pin->GetPrimaryTerminalType(); 64 | } 65 | 66 | if (TypeToPotentiallyPropagate.TerminalCategory != UEdGraphSchema_K2::PC_Wildcard) 67 | { 68 | bOutPropagated = true; 69 | TypeToPropagete = TypeToPotentiallyPropagate; 70 | } 71 | } 72 | } 73 | }; 74 | 75 | const auto TryPropagateType = [Schema](UEdGraphPin* Pin, const FEdGraphTerminalType& TerminalType, bool bTypeIsAvailable) 76 | { 77 | if(Pin) 78 | { 79 | if(bTypeIsAvailable) 80 | { 81 | const FEdGraphTerminalType PrimaryType = Pin->GetPrimaryTerminalType(); 82 | if( PrimaryType.TerminalCategory != TerminalType.TerminalCategory || 83 | PrimaryType.TerminalSubCategory != TerminalType.TerminalSubCategory || 84 | PrimaryType.TerminalSubCategoryObject != TerminalType.TerminalSubCategoryObject) 85 | { 86 | // terminal type changed: 87 | if (Pin->SubPins.Num() > 0 && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard) 88 | { 89 | Schema->RecombinePin(Pin->SubPins[0]); 90 | } 91 | 92 | Pin->PinType.PinCategory = TerminalType.TerminalCategory; 93 | Pin->PinType.PinSubCategory = TerminalType.TerminalSubCategory; 94 | Pin->PinType.PinSubCategoryObject = TerminalType.TerminalSubCategoryObject; 95 | 96 | // Also propagate the CPF_UObjectWrapper flag, which will be set for "wrapped" object ptr types (e.g. TSubclassOf). 97 | Pin->PinType.bIsUObjectWrapper = TerminalType.bTerminalIsUObjectWrapper; 98 | 99 | // Reset default values 100 | if (!Schema->IsPinDefaultValid(Pin, Pin->DefaultValue, Pin->DefaultObject, Pin->DefaultTextValue).IsEmpty()) 101 | { 102 | Schema->ResetPinToAutogeneratedDefaultValue(Pin, false); 103 | } 104 | } 105 | } 106 | else 107 | { 108 | // reset to wildcard: 109 | if (Pin->SubPins.Num() > 0) 110 | { 111 | Schema->RecombinePin(Pin->SubPins[0]); 112 | } 113 | 114 | Pin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard; 115 | Pin->PinType.PinSubCategory = NAME_None; 116 | Pin->PinType.PinSubCategoryObject = nullptr; 117 | Pin->PinType.bIsUObjectWrapper = false; 118 | Schema->ResetPinToAutogeneratedDefaultValue(Pin, false); 119 | } 120 | } 121 | }; 122 | 123 | const UFunction* TargetFunction = GetTargetFunction(); 124 | if (TargetFunction == nullptr) 125 | { 126 | return; 127 | } 128 | 129 | const FString& MapPinMetaData = TargetFunction->GetMetaData(FBlueprintMetadata::MD_MapParam); 130 | 131 | if (UEdGraphPin* MapPin = FindPinChecked(MapPinMetaData)) 132 | { 133 | bool bReadyToPropagateKeyType = true; 134 | FEdGraphTerminalType KeyTypeToPropagate; 135 | TryReadTypeToPropagate(MapPin, bReadyToPropagateKeyType, KeyTypeToPropagate); 136 | 137 | KeyTypeToPropagate.TerminalCategory = UEdGraphSchema_K2::PC_Struct; 138 | KeyTypeToPropagate.TerminalSubCategoryObject = StaticStruct(); 139 | 140 | TryPropagateType(MapPin, KeyTypeToPropagate, bReadyToPropagateKeyType); 141 | } 142 | } 143 | 144 | void UK2Node_FindComponentInMap::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const 145 | { 146 | UClass* const ActionKey = GetClass(); 147 | if (ActionRegistrar.IsOpenForRegistration(ActionKey)) 148 | { 149 | UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); 150 | check(NodeSpawner != nullptr); 151 | ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceEditor/K2Node_FindComponentInMap.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "K2Node_CallFunction.h" 7 | #include "K2Node_FindComponentInMap.generated.h" 8 | 9 | /** 10 | * Experimental helper that can lookup for a raw component pointer within TMap 11 | * 12 | * This node is a generic wrapper over function that enforces map key pin type 13 | * 14 | * @see UBlueprintComponentReferenceLibrary::Map_FindComponent 15 | */ 16 | UCLASS(MinimalAPI) 17 | class UK2Node_FindComponentInMap : public UK2Node_CallFunction 18 | { 19 | GENERATED_BODY() 20 | public: 21 | UK2Node_FindComponentInMap(); 22 | 23 | virtual FText GetMenuCategory() const override; 24 | virtual void AllocateDefaultPins() override; 25 | virtual void NotifyPinConnectionListChanged(UEdGraphPin* Pin) override; 26 | virtual void PostReconstructNode() override; 27 | 28 | void ConformPinTypes(); 29 | 30 | virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; 31 | }; 32 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestActor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | 4 | #include "BCRTestActor.h" 5 | #include "BCRTestActorComponent.h" 6 | #include "Components/ChildActorComponent.h" 7 | #include "GameFramework/DefaultPawn.h" 8 | 9 | DEFINE_LOG_CATEGORY_STATIC(LogBlueprintComponentRef, Log, All); 10 | 11 | const FName ABCRCachedTestActor::MeshPropertyName = TEXT("Mesh"); 12 | 13 | // Sets default values 14 | ABCRTestActor::ABCRTestActor(const FObjectInitializer& ObjectInitializer) 15 | : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName, UBCRTestMovementComponent::StaticClass())) 16 | { 17 | bIsEditorOnlyActor = true; 18 | 19 | Default_Root = CreateDefaultSubobject("Default_Root"); 20 | Default_Root->SetupAttachment(GetRootComponent()); 21 | 22 | Default_LevelOne = CreateDefaultSubobject("Default_LevelOne"); 23 | Default_LevelOne->SetupAttachment(Default_Root); 24 | 25 | Default_LevelTwo = CreateDefaultSubobject("Default_LevelTwo"); 26 | Default_LevelTwo->SetupAttachment(Default_LevelOne); 27 | 28 | Default_LevelZero = CreateDefaultSubobject("Default_LevelZero"); 29 | 30 | ReferenceVar = FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_Root)); 31 | ReferencePath = FBlueprintComponentReference::ForPath(FName("Default_LevelZero")); 32 | ReferenceBadVar = FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, NonExistingComponent)); 33 | ReferenceBadPath = FBlueprintComponentReference::ForPath(FName("Non_Existent_Path")); 34 | ReferenceBadValue = FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelTwo)); 35 | 36 | ReferenceArray.Empty(); 37 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelOne))); 38 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelTwo))); 39 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, NonExistingComponent))); 40 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelZero))); 41 | 42 | ReferenceSet.Empty(); 43 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelOne))); 44 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelTwo))); 45 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, NonExistingComponent))); 46 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelZero))); 47 | 48 | ReferenceMap.Empty(); 49 | ReferenceMap.Add("one", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelOne))); 50 | ReferenceMap.Add("two", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelTwo))); 51 | ReferenceMap.Add("bad", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, NonExistingComponent))); 52 | ReferenceMap.Add("zero", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ThisClass, Default_LevelZero))); 53 | } 54 | 55 | void ABCRTestActor::OnConstruction(const FTransform& Transform) 56 | { 57 | Super::OnConstruction(Transform); 58 | 59 | Construct_LevelOneNP= NewObject(this, "Construct_LevelOne_SomeName"); 60 | Construct_LevelOneNP->SetupAttachment(Default_LevelOne); 61 | Construct_LevelOneNP->RegisterComponent(); 62 | // no addinstancedcomp 63 | 64 | Construct_LevelOne = NewObject(this, "Construct_LevelOne"); 65 | Construct_LevelOne->SetupAttachment(Default_LevelOne); 66 | Construct_LevelOne->RegisterComponent(); 67 | AddInstanceComponent(Construct_LevelOne); 68 | 69 | Construct_LevelZero = NewObject(this, "Construct_LevelZero"); 70 | Construct_LevelZero->RegisterComponent(); 71 | AddInstanceComponent(Construct_LevelZero); 72 | 73 | } 74 | 75 | void ABCRTestActor::BeginPlay() 76 | { 77 | Super::BeginPlay(); 78 | 79 | Playtime_LevelOneNP= NewObject(this, "Playtime_LevelOne_SomeName"); 80 | Playtime_LevelOneNP->SetupAttachment(Default_LevelOne); 81 | Playtime_LevelOneNP->RegisterComponent(); 82 | // no addinstancedcomp 83 | 84 | Playtime_LevelOne= NewObject(this, "Playtime_LevelOne"); 85 | Playtime_LevelOne->SetupAttachment(Default_LevelOne); 86 | Playtime_LevelOne->RegisterComponent(); 87 | AddInstanceComponent(Playtime_LevelOne); 88 | 89 | Playtime_LevelZero = NewObject(this, "Playtime_LevelZero"); 90 | Playtime_LevelZero->RegisterComponent(); 91 | AddInstanceComponent(Playtime_LevelZero); 92 | 93 | } 94 | 95 | void ABCRTestActor::DumpComponents() 96 | { 97 | for (UActorComponent* Component : GetComponents()) 98 | { 99 | auto Class = Component->GetClass()->GetName(); 100 | auto Name = Component->GetName(); 101 | auto Path = Component->GetPathName(this); 102 | 103 | UE_LOG(LogBlueprintComponentRef, Log, TEXT("Found component: %p Class=%s Name=%s Path=%s"), this, *Class, *Name, *Path); 104 | } 105 | } 106 | 107 | ABCRTestActorWithChild::ABCRTestActorWithChild() 108 | { 109 | LevelNope = CreateDefaultSubobject("LevelNope"); 110 | LevelNope->SetChildActorClass(ADefaultPawn::StaticClass()); 111 | } 112 | 113 | ABCRCachedTestActor::ABCRCachedTestActor() 114 | { 115 | ReferenceSingle = FBlueprintComponentReference::ForPath(ACharacter::MeshComponentName); 116 | 117 | ReferenceArray.Add(FBlueprintComponentReference::ForPath(ACharacter::MeshComponentName)); 118 | 119 | ReferenceMap.Add("property", FBlueprintComponentReference::ForProperty("Mesh")); 120 | ReferenceMap.Add("path", FBlueprintComponentReference::ForPath(ACharacter::MeshComponentName)); 121 | } 122 | 123 | void ABCRCachedTestActor::TryCompileTemplates() 124 | { 125 | check(!"This is just to ensure template code compiles during autobuild"); 126 | 127 | AActor* PtrToActor = nullptr; 128 | USceneComponent* PtrToComponent = nullptr; 129 | FName None("SuperKey"); 130 | 131 | CachedReferenceSingle.Get(); 132 | CachedReferenceSingle.Get(PtrToActor); 133 | CachedReferenceSingle.InvalidateCache(); 134 | CachedReferenceSingle.WarmupCache(PtrToActor); 135 | 136 | CachedReferenceSingleRaw.Get(); 137 | CachedReferenceSingleRaw.Get(PtrToActor); 138 | CachedReferenceSingleRaw.WarmupCache(PtrToActor); 139 | 140 | CachedReferenceArray.Get(0); 141 | CachedReferenceArray.Get(PtrToActor, 0); 142 | CachedReferenceArray.Num(); 143 | CachedReferenceArray.IsEmpty(); 144 | CachedReferenceArray.InvalidateCache(); 145 | CachedReferenceArray.WarmupCache(PtrToActor); 146 | 147 | CachedReferenceArrayRaw.Get(0); 148 | CachedReferenceArrayRaw.Get(PtrToActor, 0); 149 | CachedReferenceArrayRaw.WarmupCache(PtrToActor); 150 | 151 | CachedReferenceMap.Get(None); 152 | CachedReferenceMap.Get(PtrToActor, None); 153 | CachedReferenceMap.Num(); 154 | CachedReferenceMap.IsEmpty(); 155 | CachedReferenceMap.InvalidateCache(); 156 | CachedReferenceMap.WarmupCache(PtrToActor); 157 | 158 | CachedReferenceMapRaw.Get("root"); 159 | CachedReferenceMapRaw.Get(PtrToActor, "root"); 160 | CachedReferenceMapRaw.WarmupCache(PtrToActor); 161 | 162 | CachedReferenceMapKey.Get(PtrToComponent); 163 | CachedReferenceMapKey.Get(PtrToActor, PtrToComponent); 164 | CachedReferenceMapKey.WarmupCache(PtrToActor); 165 | 166 | FReferenceCollector& Collector = *(FReferenceCollector*)nullptr; 167 | CachedReferenceSingleRaw.AddReferencedObjects(Collector, PtrToActor); 168 | CachedReferenceArrayRaw.AddReferencedObjects(Collector, PtrToActor); 169 | CachedReferenceMapRaw.AddReferencedObjects(Collector, PtrToActor); 170 | CachedReferenceMapKey.AddReferencedObjects(Collector, PtrToActor); 171 | } 172 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestActor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BlueprintComponentReference.h" 7 | #include "GameFramework/Actor.h" 8 | #include "GameFramework/Info.h" 9 | #include "Engine/EngineTypes.h" 10 | #include "Engine/DataAsset.h" 11 | #include "UObject/StrongObjectPtr.h" 12 | #include "GameplayTagsManager.h" 13 | #include "GameplayTagContainer.h" 14 | #include "GameplayTagAssetInterface.h" 15 | #include "CachedBlueprintComponentReference.h" 16 | #include "GameFramework/Character.h" 17 | #include "BCRTestStruct.h" 18 | #include "BCRTestActorComponent.h" 19 | 20 | #include "BCRTestActor.generated.h" 21 | 22 | // basic examples 23 | UCLASS(MinimalAPI, Blueprintable, HideCategories=("ActorTick", "Cooking", "ComponentReplication", Physics, Activation, Rendering, Transform, Tags, "ComponentTick", "Collision", "Advanced", "Replication")) 24 | class ABCRTestActor : public ACharacter 25 | { 26 | GENERATED_BODY() 27 | 28 | public: 29 | ABCRTestActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); 30 | 31 | virtual void OnConstruction(const FTransform& Transform) override; 32 | virtual void BeginPlay() override; 33 | 34 | UFUNCTION(CallInEditor) 35 | void DumpComponents(); 36 | public: 37 | 38 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 39 | TObjectPtr Default_Root; 40 | 41 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 42 | TObjectPtr Default_LevelOne; 43 | 44 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 45 | TObjectPtr Default_LevelTwo; 46 | 47 | // no uproperty, never set, just to have pretty GET_MEMBER_NAME_CHECKED 48 | TObjectPtr NonExistingComponent; 49 | 50 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 51 | TObjectPtr Default_LevelZero; 52 | 53 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 54 | TObjectPtr Construct_LevelOneNP; 55 | 56 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 57 | TObjectPtr Construct_LevelOne; 58 | 59 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 60 | TObjectPtr Construct_LevelZero; 61 | 62 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 63 | TObjectPtr Playtime_LevelOneNP; 64 | 65 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 66 | TObjectPtr Playtime_LevelOne; 67 | 68 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 69 | TObjectPtr Playtime_LevelZero; 70 | 71 | protected: 72 | 73 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test") 74 | TObjectPtr TargetDA; 75 | 76 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Engine") 77 | FBaseComponentReference BaseComponentReference; 78 | 79 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Engine") 80 | FComponentReference ComponentReference; 81 | 82 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Engine") 83 | FSoftComponentReference SoftComponentReference; 84 | 85 | // Simple component reference. Defaults only 86 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Base") 87 | FBlueprintComponentReference ReferenceVar; 88 | 89 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Base") 90 | FBlueprintComponentReference ReferencePath; 91 | 92 | // Reference to nonexistent var 93 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Base") 94 | FBlueprintComponentReference ReferenceBadVar; 95 | // Reference to nonexistent path 96 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Base") 97 | FBlueprintComponentReference ReferenceBadPath; 98 | // Reference to existing component that does not match filter conditions 99 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Base", meta=(AllowedClasses="/Script/Engine.MovementComponent")) 100 | FBlueprintComponentReference ReferenceBadValue; 101 | 102 | // Simple component reference. Only SceneComp allowed 103 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Filter", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 104 | FBlueprintComponentReference ReferenceFilterA; 105 | 106 | // Simple component reference. SceneComp disallowed 107 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test|Filter", meta=(DisallowedClasses="/Script/Engine.SceneComponent")) 108 | FBlueprintComponentReference ReferenceFilterB; 109 | 110 | // Hide clear button 111 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", NoClear, meta=(NoClear)) 112 | FBlueprintComponentReference ReferenceNoClear; 113 | 114 | // Hide navigate button 115 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(NoNavigate)) 116 | FBlueprintComponentReference ReferenceNoNavigate; 117 | 118 | // Hide navigate picker button and allow manual editing of members 119 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(NoPicker)) 120 | FBlueprintComponentReference ReferenceNoPicker; 121 | 122 | // Display only natively created components 123 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(ShowNative=true, ShowBlueprint=false, ShowInstanced=false, ShowHidden=false)) 124 | FBlueprintComponentReference ReferenceNativeOnly; 125 | // Display only blueprint created components 126 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(ShowNative=false, ShowBlueprint=true, ShowInstanced=false, ShowHidden=false)) 127 | FBlueprintComponentReference ReferenceBlueprintOnly; 128 | // Display only instanced created components. Instance only 129 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(ShowNative=false, ShowBlueprint=false, ShowInstanced=true, ShowHidden=false)) 130 | FBlueprintComponentReference ReferenceInstancedOnly; 131 | // Display components without properties assigned. Instance only 132 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(ShowNative=false, ShowBlueprint=false, ShowInstanced=false, ShowHidden=true)) 133 | FBlueprintComponentReference ReferencePathOnly; 134 | 135 | // Display only natively created components. No editor 136 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Metadata", meta=(ShowNative=true, ShowBlueprint=false, ShowInstanced=false, ShowHidden=false, ShowEditor=false)) 137 | FBlueprintComponentReference ReferenceNativeOnlyNoEditor; 138 | 139 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Containers", meta=(ShowBlueprint=false, AllowedClasses="/Script/Engine.SceneComponent")) 140 | TArray ReferenceArray; 141 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Containers", meta=(ShowBlueprint=false, AllowedClasses="/Script/Engine.SceneComponent")) 142 | TMap ReferenceMap; 143 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Containers", meta=(ShowBlueprint=false, AllowedClasses="/Script/Engine.SceneComponent")) 144 | TSet ReferenceSet; 145 | 146 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Struct") 147 | FBCRTestStruct StructTest; 148 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test|Struct") 149 | TArray StructTestArray; 150 | 151 | }; 152 | 153 | UCLASS(MinimalAPI, Blueprintable) 154 | class ABCRTestActorWithChild : public ABCRTestActor 155 | { 156 | GENERATED_BODY() 157 | public: 158 | ABCRTestActorWithChild(); 159 | // unsupported, can reference only component, not instanced things within spawned actor 160 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Components) 161 | TObjectPtr LevelNope; 162 | 163 | }; 164 | 165 | // test cases with cached access 166 | UCLASS(MinimalAPI, Blueprintable) 167 | class ABCRCachedTestActor : public ACharacter 168 | { 169 | GENERATED_BODY() 170 | 171 | public: 172 | static const FName MeshPropertyName; 173 | 174 | ABCRCachedTestActor(); 175 | 176 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test|Cached", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 177 | FBlueprintComponentReference ReferenceSingle; 178 | 179 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test|Cached", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 180 | TArray ReferenceArray; 181 | 182 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test|Cached", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 183 | TMap ReferenceMap; 184 | 185 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test|Cached", meta=(AllowedClasses="/Script/Engine.SceneComponent")) 186 | TMap ReferenceMapKey; 187 | 188 | TCachedComponentReferenceSingle CachedReferenceSingle { this, &ReferenceSingle }; 189 | 190 | TCachedComponentReferenceSingle CachedReferenceSingleRaw { this, &ReferenceSingle }; 191 | 192 | TCachedComponentReferenceArray CachedReferenceArray { this, &ReferenceArray }; 193 | 194 | TCachedComponentReferenceArray CachedReferenceArrayRaw { this, &ReferenceArray }; 195 | 196 | TCachedComponentReferenceMapValue CachedReferenceMap { this, &ReferenceMap }; 197 | 198 | TCachedComponentReferenceMapValue CachedReferenceMapRaw { this, &ReferenceMap }; 199 | 200 | TCachedComponentReferenceMapKey CachedReferenceMapKey { this, &ReferenceMapKey }; 201 | 202 | void TryCompileTemplates(); 203 | }; 204 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestActorComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BCRTestActorComponent.h" 4 | 5 | 6 | // Sets default values for this component's properties 7 | UBCRTestActorComponent::UBCRTestActorComponent() 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestActorComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BlueprintComponentReference.h" 7 | #include "GameFramework/Actor.h" 8 | #include "Components/ActorComponent.h" 9 | #include "Components/SceneComponent.h" 10 | #include "GameFramework/CharacterMovementComponent.h" 11 | #include "BCRTestActorComponent.generated.h" 12 | 13 | 14 | UCLASS(MinimalAPI, meta=(BlueprintSpawnableComponent)) 15 | class UBCRTestActorComponent : public UActorComponent 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | // Sets default values for this component's properties 21 | UBCRTestActorComponent(); 22 | 23 | UPROPERTY() 24 | FName SampleName; 25 | 26 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test") 27 | FBlueprintComponentReference Reference; 28 | }; 29 | 30 | UCLASS(MinimalAPI, meta=(BlueprintSpawnableComponent)) 31 | class UBCRTestSceneComponent : public USceneComponent 32 | { 33 | GENERATED_BODY() 34 | 35 | public: 36 | 37 | UPROPERTY() 38 | FName SampleName; 39 | 40 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test") 41 | FBlueprintComponentReference Reference; 42 | }; 43 | 44 | UCLASS(MinimalAPI, meta=(BlueprintSpawnableComponent)) 45 | class UBCRTestMovementComponent : public UCharacterMovementComponent 46 | { 47 | GENERATED_BODY() 48 | 49 | public: 50 | UPROPERTY() 51 | FName SampleName; 52 | 53 | 54 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test") 55 | FBlueprintComponentReference Reference; 56 | }; 57 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestDataAsset.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | 4 | #include "BCRTestDataAsset.h" 5 | #include "BCRTestActor.h" 6 | 7 | UBCRTestDataAsset::UBCRTestDataAsset() 8 | { 9 | ReferenceSingle = FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelOne)); 10 | 11 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_Root))); 12 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelOne))); 13 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelTwo))); 14 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, NonExistingComponent))); 15 | ReferenceArray.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelZero))); 16 | 17 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_Root))); 18 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelOne))); 19 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelTwo))); 20 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, NonExistingComponent))); 21 | ReferenceSet.Add(FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelZero))); 22 | 23 | ReferenceMap.Add("root", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_Root))); 24 | ReferenceMap.Add("first", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelOne))); 25 | ReferenceMap.Add("second", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelTwo))); 26 | ReferenceMap.Add("bad", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, NonExistingComponent))); 27 | ReferenceMap.Add("bad2", FBlueprintComponentReference::ForProperty(GET_MEMBER_NAME_CHECKED(ABCRTestActor, Default_LevelZero))); 28 | } 29 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestDataAsset.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BlueprintComponentReference.h" 7 | #include "CachedBlueprintComponentReference.h" 8 | #include "Engine/DataAsset.h" 9 | #include "Components/SceneComponent.h" 10 | #include "GameFramework/Actor.h" 11 | #include "BCRTestDataAsset.generated.h" 12 | 13 | /** 14 | * 15 | */ 16 | UCLASS(MinimalAPI, Blueprintable) 17 | class UBCRTestDataAsset : public UDataAsset 18 | { 19 | GENERATED_BODY() 20 | public: 21 | UPROPERTY(EditAnywhere, meta=(ActorClass="/Script/BlueprintComponentReferenceTests.BCRTestActor"), Category=Test) 22 | FBlueprintComponentReference ExternalRef; 23 | 24 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test", meta=(ActorClass="/Script/BlueprintComponentReferenceTests.BCRTestActor", AllowedClasses="/Script/Engine.SceneComponent")) 25 | FBlueprintComponentReference ReferenceSingle; 26 | 27 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test", meta=(ActorClass="/Script/BlueprintComponentReferenceTests.BCRTestActor", AllowedClasses="/Script/Engine.SceneComponent")) 28 | TArray ReferenceArray; 29 | 30 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test", meta=(ActorClass="/Script/BlueprintComponentReferenceTests.BCRTestActor", AllowedClasses="/Script/Engine.SceneComponent")) 31 | TSet ReferenceSet; 32 | 33 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Test", meta=(ActorClass="/Script/BlueprintComponentReferenceTests.BCRTestActor", AllowedClasses="/Script/Engine.SceneComponent")) 34 | TMap ReferenceMap; 35 | 36 | UBCRTestDataAsset(); 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestStruct.cpp: -------------------------------------------------------------------------------- 1 | #include "BCRTestStruct.h" 2 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BCRTestStruct.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BlueprintComponentReference.h" 4 | #include "BCRTestStruct.generated.h" 5 | 6 | USTRUCT(BlueprintType) 7 | struct FBCRTestStruct 8 | { 9 | GENERATED_BODY() 10 | 11 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test") 12 | FBlueprintComponentReference Reference; 13 | 14 | UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Test") 15 | TArray ReferenceArray; 16 | }; 17 | 18 | USTRUCT(BlueprintType) 19 | struct FBCRTestStrustData 20 | { 21 | GENERATED_BODY() 22 | 23 | UPROPERTY(EditAnywhere,Category="Test") 24 | int32 Data = 0; 25 | UPROPERTY(EditAnywhere,Category="Test") 26 | FName Sample; 27 | }; 28 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BlueprintComponentReferenceTests.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class BlueprintComponentReferenceTests : ModuleRules 6 | { 7 | // This is to emulate engine installation and verify includes during development 8 | // Gives effect similar to BuildPlugin with -StrictIncludes 9 | public bool bStrictIncludesCheck = false; 10 | 11 | public BlueprintComponentReferenceTests(ReadOnlyTargetRules Target) : base(Target) 12 | { 13 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 14 | 15 | if (bStrictIncludesCheck) 16 | { 17 | bUseUnity = false; 18 | PCHUsage = PCHUsageMode.NoPCHs; 19 | // Enable additional checks used for Engine modules 20 | bTreatAsEngineModule = true; 21 | } 22 | 23 | PublicIncludePaths.Add(ModuleDirectory); 24 | 25 | PublicDependencyModuleNames.AddRange(new string[] { 26 | "Core", 27 | "CoreUObject", 28 | "Engine", 29 | "BlueprintComponentReference", 30 | "BlueprintComponentReferenceEditor" 31 | }); 32 | 33 | if (Target.Version.MajorVersion >= 5) 34 | { 35 | PrivateDependencyModuleNames.AddRange(new string[] { 36 | "AutomationTest" 37 | }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Source/BlueprintComponentReferenceTests/BlueprintComponentReferenceTests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Aquanox. 2 | 3 | #include "BCRTestActor.h" 4 | #include "BCRTestDataAsset.h" 5 | #include "BCRTestActorComponent.h" 6 | #include "BlueprintComponentReference.h" 7 | #include "BlueprintComponentReferenceLibrary.h" 8 | #include "BlueprintComponentReferenceMetadata.h" 9 | #include "CachedBlueprintComponentReference.h" 10 | #include "GameFramework/Actor.h" 11 | #include "GameFramework/Character.h" 12 | #include "Stats/StatsMisc.h" 13 | #include "Misc/AutomationTest.h" 14 | #include "Engine/Engine.h" 15 | #include "Engine/World.h" 16 | #include "Components/SkeletalMeshComponent.h" 17 | #include "Components/StaticMeshComponent.h" 18 | #include "GameFramework/CharacterMovementComponent.h" 19 | #include "Modules/ModuleManager.h" 20 | 21 | IMPLEMENT_MODULE(FDefaultModuleImpl, BlueprintComponentReferenceTests); 22 | 23 | #if WITH_DEV_AUTOMATION_TESTS 24 | 25 | struct FTestWorldScope 26 | { 27 | FWorldContext WorldContext; 28 | UWorld* World = nullptr; 29 | UWorld* PrevWorld = nullptr; 30 | 31 | FTestWorldScope() 32 | { 33 | World = UWorld::CreateWorld(EWorldType::Game, false); 34 | 35 | WorldContext = GEngine->CreateNewWorldContext(EWorldType::Game); 36 | WorldContext.SetCurrentWorld(World); 37 | 38 | PrevWorld = &*GWorld; 39 | GWorld = World; 40 | } 41 | 42 | ~FTestWorldScope() 43 | { 44 | GEngine->DestroyWorldContext(World); 45 | World->DestroyWorld(true); 46 | GWorld = PrevWorld; 47 | } 48 | 49 | UWorld* operator->() const { return World; } 50 | }; 51 | 52 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBlueprintComponentReferenceTests_Core, 53 | "BlueprintComponentReference.Core", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter | EAutomationTestFlags::HighPriority); 54 | 55 | bool FBlueprintComponentReferenceTests_Core::RunTest(FString const&) 56 | { 57 | FTestWorldScope World; 58 | 59 | ABCRTestActor* const TestActorNull = nullptr; 60 | ABCRTestActor* const TestActorS = World->SpawnActor(); 61 | ABCRTestActor* const TestActorD = GetMutableDefault(); 62 | 63 | for(ABCRTestActor* TestActor : { TestActorNull, TestActorS, TestActorD }) 64 | { 65 | UActorComponent* const RootComponent = 66 | TestActor ? TestActor->GetRootComponent() : nullptr; 67 | UActorComponent* const TestRootComponent = 68 | TestActor ? TestActor->Default_Root : nullptr; 69 | UActorComponent* const LevelOneComponent = 70 | TestActor ? TestActor->Default_LevelOne : nullptr; 71 | UActorComponent* const LevelOneConstructNPComponent = 72 | TestActor ? FindObject(TestActor, TEXT("Construct_LevelOne_SomeName")) : nullptr; 73 | 74 | AddInfo(FString::Printf(TEXT("Using actor %s"), * GetNameSafe(TestActor))); 75 | 76 | { 77 | FBlueprintComponentReference Reference; 78 | TestTrueExpr(Reference.IsNull()); 79 | TestTrueExpr(Reference.ToString() == TEXT("")); 80 | TestTrueExpr(Reference.GetComponent(TestActor) == nullptr); 81 | } 82 | 83 | { 84 | FName SampleName0 = "Sample"; 85 | FName SampleName1 = "Sample"; 86 | SampleName1.SetNumber(11); 87 | FName SampleName2 = "Sample"; 88 | SampleName2.SetNumber(22); 89 | 90 | 91 | FBlueprintComponentReference Sample0 = FBlueprintComponentReference::ForPath(SampleName0); 92 | FBlueprintComponentReference Sample1 = FBlueprintComponentReference::ForPath(SampleName1); 93 | FBlueprintComponentReference Sample2 = FBlueprintComponentReference::ForPath(SampleName2); 94 | TestTrueExpr(Sample0 != Sample1); 95 | TestTrueExpr(Sample0 != Sample2); 96 | TestTrueExpr(Sample1 != Sample2); 97 | TestTrueExpr(Sample0.ToString() == TEXT("path:Sample")); 98 | TestTrueExpr(Sample1.ToString() == TEXT("path:Sample_10")); 99 | TestTrueExpr(Sample2.ToString() == TEXT("path:Sample_21")); 100 | } 101 | 102 | { 103 | FBlueprintComponentReference BasicReference; 104 | BasicReference.ParseString(TEXT("Default_LevelOne")); 105 | 106 | TestTrueExpr(BasicReference == FBlueprintComponentReference(EBlueprintComponentReferenceMode::Property, "Default_LevelOne")); 107 | TestTrueExpr(BasicReference == FBlueprintComponentReference(TEXT("property:Default_LevelOne"))); 108 | TestTrueExpr(BasicReference != FBlueprintComponentReference(TEXT("Default_LevelOne_f34t25tg2"))); 109 | TestTrueExpr(!BasicReference.IsNull()); 110 | TestTrueExpr(BasicReference.ToString() == TEXT("property:Default_LevelOne")); 111 | TestTrueExpr(BasicReference.GetComponent(TestActor) == LevelOneComponent); 112 | } 113 | 114 | { 115 | FBlueprintComponentReference PropReference; 116 | PropReference.ParseString(TEXT("property:Default_Root")); 117 | 118 | TestTrueExpr(PropReference == FBlueprintComponentReference(EBlueprintComponentReferenceMode::Property, "Default_Root")); 119 | TestTrueExpr(PropReference == FBlueprintComponentReference(TEXT("property:Default_Root"))); 120 | TestTrueExpr(PropReference != FBlueprintComponentReference(TEXT("path:Default_Root"))); 121 | TestTrueExpr(!PropReference.IsNull()); 122 | TestTrueExpr(PropReference.ToString() == TEXT("property:Default_Root")); 123 | TestTrueExpr(PropReference.GetComponent(TestActor) == TestRootComponent); 124 | } 125 | 126 | { 127 | FBlueprintComponentReference PathReference; 128 | PathReference.ParseString(TEXT("path:Construct_LevelOne_SomeName")); 129 | 130 | TestTrueExpr(PathReference == FBlueprintComponentReference(EBlueprintComponentReferenceMode::Path, "Construct_LevelOne_SomeName")); 131 | TestTrueExpr(PathReference != FBlueprintComponentReference(TEXT("Construct_LevelOne_SomeName"))); 132 | TestTrueExpr(PathReference == FBlueprintComponentReference(TEXT("path:Construct_LevelOne_SomeName"))); 133 | TestTrueExpr(!PathReference.IsNull()); 134 | TestTrueExpr(PathReference.ToString() == TEXT("path:Construct_LevelOne_SomeName")); 135 | TestTrueExpr(PathReference.GetComponent(TestActor) == LevelOneConstructNPComponent); 136 | } 137 | 138 | { 139 | FBlueprintComponentReference BadReference; 140 | BadReference.ParseString(TEXT("DoesNotExist")); 141 | 142 | TestTrueExpr(BadReference == FBlueprintComponentReference(EBlueprintComponentReferenceMode::Property, "DoesNotExist")); 143 | TestTrueExpr(BadReference == FBlueprintComponentReference(TEXT("DoesNotExist"))); 144 | TestTrueExpr(!BadReference.IsNull()); 145 | TestTrueExpr(BadReference.GetComponent(TestActor) == nullptr); 146 | } 147 | } 148 | 149 | return true; 150 | } 151 | 152 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBlueprintComponentReferenceTests_Library, 153 | "BlueprintComponentReference.Library", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter | EAutomationTestFlags::HighPriority); 154 | 155 | bool FBlueprintComponentReferenceTests_Library::RunTest(FString const&) 156 | { 157 | FTestWorldScope World; 158 | 159 | ABCRTestActor* const TestActorNull = nullptr; 160 | ABCRTestActor* const TestActorReal = World->SpawnActor(); 161 | ABCRTestActor* const TestActorDefault = GetMutableDefault(); 162 | 163 | FBlueprintComponentReference NullReference; 164 | FBlueprintComponentReference TestBaseReference("property:Default_Root"); 165 | FBlueprintComponentReference MeshPathReference(EBlueprintComponentReferenceMode::Path, ACharacter::MeshComponentName); 166 | FBlueprintComponentReference MeshVarReference(EBlueprintComponentReferenceMode::Property, TEXT("Mesh")); 167 | FBlueprintComponentReference BadReference("property:DoesNotExist"); 168 | 169 | TestTrueExpr(UBlueprintComponentReferenceLibrary::IsNullComponentReference(NullReference)); 170 | TestTrueExpr(!UBlueprintComponentReferenceLibrary::IsNullComponentReference(TestBaseReference)); 171 | 172 | FBlueprintComponentReference CopyReference(TestBaseReference); 173 | TestTrue("InvalidateComponentReference.1", !CopyReference.IsNull()); 174 | UBlueprintComponentReferenceLibrary::InvalidateComponentReference(CopyReference); 175 | TestTrue("InvalidateComponentReference.2", CopyReference.IsNull()); 176 | 177 | UActorComponent* Result = nullptr; 178 | 179 | // TryGetReferencedComponent and GetReferencedComponent same 180 | TestFalse("InvalidThings.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(BadReference, TestActorNull, UActorComponent::StaticClass(), Result)); 181 | TestTrue("InvalidThings.GetReferencedComponent.Result", Result == nullptr); 182 | 183 | TestFalse("Null.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(NullReference, TestActorReal, nullptr, Result)); 184 | TestTrue("Null.GetReferencedComponent.Result", Result == nullptr); 185 | 186 | TestTrue("TestBase.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(TestBaseReference, TestActorReal, nullptr, Result)); 187 | TestTrue("TestBase.GetReferencedComponent.Result", Result == TestActorReal->Default_Root); 188 | 189 | TestFalse("TestBase2.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(TestBaseReference, TestActorReal, USkeletalMeshComponent::StaticClass(), Result)); 190 | TestTrue("TestBase2.GetReferencedComponent.Result", Result == nullptr); 191 | 192 | TestTrue("MeshPathReference.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(MeshPathReference, TestActorReal, nullptr, Result)); 193 | TestTrue("MeshPathReference.GetReferencedComponent.Result", Result == TestActorReal->GetMesh()); 194 | 195 | TestTrue("MeshPathReference2.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(MeshPathReference, TestActorReal, USkeletalMeshComponent::StaticClass(), Result)); 196 | TestTrue("MeshPathReference2.GetReferencedComponent.Result", Result == TestActorReal->GetMesh()); 197 | 198 | TestFalse("MeshPathReference3.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(MeshPathReference, TestActorReal, UStaticMeshComponent::StaticClass(), Result)); 199 | TestTrue("MeshPathReference3.GetReferencedComponent.Result", Result == nullptr); 200 | 201 | TestTrue("MeshVarReference.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(MeshVarReference, TestActorReal, nullptr, Result)); 202 | TestTrue("MeshVarReference.GetReferencedComponent.Result", Result == TestActorReal->GetMesh()); 203 | 204 | TestTrue("MeshVarReference2.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(MeshVarReference, TestActorReal, USkeletalMeshComponent::StaticClass(), Result)); 205 | TestTrue("MeshVarReference2.GetReferencedComponent.Result", Result == TestActorReal->GetMesh()); 206 | 207 | TestFalse("MeshVarReference3.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(MeshVarReference, TestActorReal, UStaticMeshComponent::StaticClass(), Result)); 208 | TestTrue("MeshVarReference3.GetReferencedComponent.Result", Result == nullptr); 209 | 210 | TestFalse("Bad.GetReferencedComponent", UBlueprintComponentReferenceLibrary::GetReferencedComponent(BadReference, TestActorReal, nullptr, Result)); 211 | TestTrue("Bad.GetReferencedComponent.Result", Result == nullptr); 212 | 213 | return true; 214 | } 215 | 216 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBlueprintComponentReferenceTests_Cached, 217 | "BlueprintComponentReference.Cached", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter | EAutomationTestFlags::HighPriority); 218 | 219 | bool FBlueprintComponentReferenceTests_Cached::RunTest(FString const&) 220 | { 221 | FTestWorldScope World; 222 | 223 | auto* TestActor = World->SpawnActor(); 224 | TestTrueExpr(TestActor != nullptr); 225 | TestActor->ReferenceSingle.Invalidate(); 226 | TestActor->ReferenceArray.Empty(); 227 | TestActor->ReferenceMap.Empty(); 228 | TestActor->ReferenceMapKey.Empty(); 229 | 230 | //====================================== 231 | 232 | USceneComponent* ExpectedComp = TestActor->GetMesh(); 233 | TestActor->ReferenceSingle = FBlueprintComponentReference::ForPath(ABCRCachedTestActor::MeshComponentName); 234 | 235 | TestTrueExpr(ExpectedComp != nullptr); 236 | TestTrueExpr(!TestActor->ReferenceSingle.IsNull()); 237 | TestTrueExpr(TestActor->ReferenceSingle.GetComponent(TestActor) == ExpectedComp); 238 | TestTrueExpr(TestActor == TestActor->CachedReferenceSingle.GetBaseActor()); 239 | TestTrueExpr(TestActor->CachedReferenceSingle.Get() == ExpectedComp); 240 | TestTrueExpr(TestActor->CachedReferenceSingle.Get(TestActor) == ExpectedComp); 241 | 242 | //====================================== 243 | 244 | TArray ExpectedComps; 245 | TArray ExpectedKeys; 246 | 247 | for (int i = 0; i < 4; ++i) 248 | { 249 | auto* Comp = NewObject(TestActor); 250 | Comp->SampleName = *FString::Printf(TEXT("MapKey%p"), Comp); 251 | Comp->SetupAttachment(TestActor->GetRootComponent()); 252 | Comp->RegisterComponent(); 253 | 254 | ExpectedComps.Add(Comp); 255 | 256 | UE_LOG(LogTemp, Log, TEXT("Make component. Ptr=%p Name=%s PathName=%s"), Comp, *Comp->GetName(), *Comp->GetPathName(TestActor)); 257 | 258 | TestActor->ReferenceArray.Add(FBlueprintComponentReference::ForPath(Comp->GetFName())); 259 | 260 | ExpectedKeys.Add(Comp->SampleName); 261 | TestActor->ReferenceMap.Add(Comp->SampleName, FBlueprintComponentReference::ForPath(Comp->GetFName())); 262 | 263 | FBCRTestStrustData Data; 264 | Data.Sample = Comp->SampleName; 265 | TestActor->ReferenceMapKey.Add(FBlueprintComponentReference::ForPath(Comp->GetFName()), Data); 266 | } 267 | 268 | TestTrueExpr(ExpectedComps.Num() == TestActor->ReferenceArray.Num() ); 269 | TestTrueExpr(TestActor == TestActor->CachedReferenceArray.GetBaseActorPtr() ); 270 | TestTrueExpr(ExpectedComps.Num() == TestActor->CachedReferenceArray.Num() ); 271 | 272 | TestTrueExpr(TestActor->ReferenceArray[0].GetComponent(TestActor) == ExpectedComps[0]); 273 | TestTrueExpr(TestActor->ReferenceArray[1].GetComponent(TestActor) == ExpectedComps[1]); 274 | TestTrueExpr(TestActor->ReferenceArray[2].GetComponent(TestActor) == ExpectedComps[2]); 275 | TestTrueExpr(TestActor->ReferenceArray[3].GetComponent(TestActor) == ExpectedComps[3]); 276 | 277 | TestTrueExpr(TestActor->CachedReferenceArray.Get(TestActor, 0) == ExpectedComps[0]); 278 | TestTrueExpr(TestActor->CachedReferenceArray.Get(TestActor, 1) == ExpectedComps[1]); 279 | TestTrueExpr(TestActor->CachedReferenceArray.Get(TestActor, 2) == ExpectedComps[2]); 280 | TestTrueExpr(TestActor->CachedReferenceArray.Get(TestActor, 3) == ExpectedComps[3]); 281 | 282 | TestTrueExpr(TestActor->CachedReferenceArray.Get(0) == ExpectedComps[0]); 283 | TestTrueExpr(TestActor->CachedReferenceArray.Get(1) == ExpectedComps[1]); 284 | TestTrueExpr(TestActor->CachedReferenceArray.Get(2) == ExpectedComps[2]); 285 | TestTrueExpr(TestActor->CachedReferenceArray.Get(3) == ExpectedComps[3]); 286 | 287 | //====================================== 288 | 289 | TestTrueExpr(ExpectedKeys.Num() == TestActor->ReferenceMap.Num() ); 290 | TestTrueExpr(TestActor == TestActor->CachedReferenceMap.GetBaseActor() ); 291 | 292 | for (FName ExpectedKey : ExpectedKeys) 293 | { 294 | FBlueprintComponentReference& Ref = TestActor->ReferenceMap.FindChecked(ExpectedKey); 295 | UActorComponent* Direct = Ref.GetComponent(TestActor); 296 | USceneComponent* CachedBased = TestActor->CachedReferenceMap.Get(ExpectedKey); 297 | USceneComponent* Cached = TestActor->CachedReferenceMap.Get(TestActor, ExpectedKey); 298 | TestTrueExpr(Direct != nullptr); 299 | TestTrueExpr(Cached != nullptr); 300 | TestTrueExpr(CachedBased != nullptr); 301 | TestTrueExpr(Cached == CachedBased); 302 | TestTrueExpr(Cached == Direct); 303 | } 304 | 305 | //====================================== 306 | 307 | TestTrueExpr(ExpectedKeys.Num() == TestActor->ReferenceMapKey.Num() ); 308 | TestTrueExpr(TestActor == TestActor->CachedReferenceMapKey.GetBaseActor() ); 309 | 310 | for (UActorComponent* ExpectedKey : ExpectedComps) 311 | { 312 | UBCRTestSceneComponent* InKey = Cast(ExpectedKey); 313 | UE_LOG(LogTemp, Log, TEXT("Searching key %p name=%s"), InKey, *InKey->GetName()); 314 | FBCRTestStrustData* CachedBased = TestActor->CachedReferenceMapKey.Get(InKey); 315 | FBCRTestStrustData* Cached = TestActor->CachedReferenceMapKey.Get(TestActor, InKey); 316 | UE_LOG(LogTemp, Log, TEXT("Found data %s"), *Cached->Sample.ToString()); 317 | TestTrueExpr(Cached == CachedBased); 318 | TestTrueExpr(InKey->SampleName == Cached->Sample); 319 | TestTrueExpr(InKey->SampleName == CachedBased->Sample); 320 | } 321 | 322 | return true; 323 | } 324 | 325 | #define X_NAME_OF(x) TEXT(#x) 326 | // single prop sequential resolve vs direct 327 | template 328 | struct PerfRunner_Single 329 | { 330 | AActor* Actor; 331 | FBlueprintComponentReference Ref; 332 | 333 | TCachedComponentReference CachedStrong; 334 | 335 | TCachedComponentReference CachedWeak; 336 | 337 | TCachedComponentReference CachedWarm; 338 | 339 | PerfRunner_Single(AActor* InActor, const FBlueprintComponentReference& InRef) 340 | : Actor(InActor), CachedStrong(InActor, &Ref), CachedWeak(InActor, &Ref), CachedWarm(InActor, &Ref) 341 | { 342 | Ref = InRef; 343 | } 344 | 345 | FString GenerateDescription(const TCHAR* AccessType) 346 | { 347 | return FString::Printf(TEXT("PerfRunner_Single [%-10s] [%-10s] %-8d loops"), AccessType, *Ref.ToString(), MaxNum); 348 | } 349 | 350 | void Run() 351 | { 352 | { 353 | FScopeLogTime Scope(*GenerateDescription(TEXT("Direct")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 354 | for (int32 N = 0; N < MaxNum; ++N) 355 | { 356 | Ref.GetComponent(Actor); 357 | } 358 | } 359 | 360 | { 361 | FScopeLogTime Scope(*GenerateDescription(TEXT("Strong")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 362 | for (int32 N = 0; N < MaxNum; ++N) 363 | { 364 | CachedStrong.Get(Actor); 365 | } 366 | } 367 | 368 | { 369 | FScopeLogTime Scope(*GenerateDescription(TEXT("Weak")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 370 | for (int32 N = 0; N < MaxNum; ++N) 371 | { 372 | CachedWeak.Get(Actor); 373 | } 374 | } 375 | 376 | { 377 | CachedWarm.WarmupCache(Actor); 378 | 379 | FScopeLogTime Scope(*GenerateDescription(TEXT("WWarm")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 380 | for (int32 N = 0; N < MaxNum; ++N) 381 | { 382 | CachedWarm.Get(Actor); 383 | } 384 | } 385 | } 386 | }; 387 | 388 | // array random access with resolve vs cached resolve 389 | template 390 | struct PerfRunner_Array 391 | { 392 | AActor* Actor; 393 | FBlueprintComponentReference Ref; 394 | TArray RefArray; 395 | TArray RefAccessSequence; 396 | 397 | TCachedComponentReferenceArray CachedStrong; 398 | TCachedComponentReferenceArray CachedWeak; 399 | TCachedComponentReferenceArray CachedWarm; 400 | 401 | PerfRunner_Array(AActor* InActor, const FBlueprintComponentReference& InRef) 402 | : Actor(InActor), CachedStrong(InActor, &RefArray), CachedWeak(InActor, &RefArray), CachedWarm(InActor, &RefArray) 403 | { 404 | FRandomStream RandomStream( 0xC0FFEE ); 405 | 406 | Ref = InRef; 407 | 408 | RefArray.SetNum(NumEntries); 409 | for (int32 Idx = 0; Idx < NumEntries; ++Idx) 410 | { 411 | RefArray[Idx] = InRef; 412 | } 413 | RefAccessSequence.SetNum(NumAccess); 414 | for (int32 Idx = 0; Idx < NumAccess; ++Idx) 415 | { 416 | RefAccessSequence[Idx] = RandomStream.RandHelper(NumEntries); 417 | } 418 | } 419 | 420 | FString GenerateDescription(const TCHAR* AccessType) 421 | { 422 | return FString::Printf(TEXT("PerfRunner_Array [%-10s] [%-10s] %-8d access of %-8d"), AccessType, *Ref.ToString(), NumAccess, NumEntries); 423 | } 424 | 425 | void Run() 426 | { 427 | { 428 | FScopeLogTime Scope(*GenerateDescription(TEXT("Direct")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 429 | for (int32 Index : RefAccessSequence) 430 | { 431 | RefArray[Index].GetComponent(Actor); 432 | } 433 | } 434 | { 435 | FScopeLogTime Scope(*GenerateDescription(TEXT("Strong")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 436 | for (int32 Index : RefAccessSequence) 437 | { 438 | CachedStrong.Get(Actor, Index); 439 | } 440 | } 441 | { 442 | FScopeLogTime Scope(*GenerateDescription(TEXT("Weak")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 443 | for (int32 Index : RefAccessSequence) 444 | { 445 | CachedWeak.Get(Actor, Index); 446 | } 447 | } 448 | { 449 | CachedWarm.WarmupCache(Actor); 450 | 451 | FScopeLogTime Scope(*GenerateDescription(TEXT("WWarm")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 452 | for (int32 Index : RefAccessSequence) 453 | { 454 | CachedWarm.Get(Actor, Index); 455 | } 456 | } 457 | } 458 | }; 459 | // random map key access vs loop 460 | template 461 | struct PerfRunner_MapKey 462 | { 463 | AActor* Actor; 464 | FBlueprintComponentReference Ref; 465 | 466 | TMap RefMap; 467 | TCachedComponentReferenceMapKey CachedMap; 468 | TCachedComponentReferenceMapKey CachedMapWarm; 469 | 470 | TArray Components; 471 | TArray ComponentsInUse; 472 | TArray RefAccessSequence; 473 | 474 | PerfRunner_MapKey(AActor* InActor) 475 | : Actor(InActor), CachedMap(InActor, &RefMap), CachedMapWarm(InActor, &RefMap) 476 | { 477 | FRandomStream RandomStream( 0xC0FFEE ); 478 | 479 | // setup components for test 480 | Components.Reserve(NumComponents + 10); 481 | InActor->GetComponents(UBCRTestSceneComponent::StaticClass(), Components); 482 | while (Components.Num() < NumComponents) 483 | { 484 | auto* Component = NewObject(InActor); 485 | Component->SetupAttachment(InActor->GetRootComponent()); 486 | Component->RegisterComponent(); 487 | Components.Add(Component); 488 | } 489 | 490 | // setup map contents 491 | ComponentsInUse.Reserve(NumEntries); 492 | while (RefMap.Num() < NumEntries) 493 | { 494 | int32 RIdx = RandomStream.RandHelper(Components.Num()); 495 | USceneComponent* Target = Components[RIdx]; 496 | 497 | RefMap.Add(FBlueprintComponentReference::ForPath(Target->GetFName()), RIdx); 498 | 499 | ComponentsInUse.AddUnique(Target); 500 | } 501 | 502 | // setup search sequence 503 | RefAccessSequence.Reserve(NumAccess); 504 | for (int32 Idx = 0; Idx < NumAccess; ++Idx) 505 | { 506 | RefAccessSequence.Add( ComponentsInUse[ RandomStream.RandHelper(ComponentsInUse.Num()) ] ); 507 | } 508 | } 509 | 510 | FString GenerateDescription(const TCHAR* AccessType) 511 | { 512 | return FString::Printf(TEXT("PerfRunner_MapKey [%-10s] [%-10s] %-8d access of %-8d/%-8d"), AccessType, *Ref.ToString(), NumAccess, NumEntries, NumComponents); 513 | } 514 | 515 | int DirectSearch(AActor* InActor, UActorComponent* InKey) 516 | { 517 | for (auto& RefToValue : RefMap) 518 | { 519 | if (RefToValue.Key.GetComponent(InActor) == InKey) 520 | { 521 | return RefToValue.Value; 522 | } 523 | } 524 | return -1; 525 | } 526 | 527 | void Run() 528 | { 529 | { 530 | FScopeLogTime Scope(*GenerateDescription(TEXT("Direct")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 531 | for (UActorComponent* Index : RefAccessSequence) 532 | { 533 | DirectSearch(Actor, Index); 534 | } 535 | } 536 | { 537 | FScopeLogTime Scope(*GenerateDescription(TEXT("Weak")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 538 | for (const USceneComponent* Index : RefAccessSequence) 539 | { 540 | CachedMap.Get(Actor, Index); 541 | } 542 | } 543 | { 544 | CachedMapWarm.WarmupCache(Actor); 545 | 546 | FScopeLogTime Scope(*GenerateDescription(TEXT("Warm")), nullptr, FConditionalScopeLogTime::ScopeLog_Milliseconds); 547 | for (const USceneComponent* Index : RefAccessSequence) 548 | { 549 | CachedMapWarm.Get(Actor, Index); 550 | } 551 | } 552 | } 553 | }; 554 | 555 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBlueprintComponentReferenceTests_Perf, 556 | "BlueprintComponentReference.Perf", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); 557 | 558 | bool FBlueprintComponentReferenceTests_Perf::RunTest(FString const&) 559 | { 560 | FTestWorldScope World; 561 | 562 | auto* TestActor = World->SpawnActor(); 563 | TestTrueExpr(TestActor != nullptr); 564 | TestActor->ReferenceSingle.Invalidate(); 565 | TestActor->ReferenceArray.Empty(); 566 | TestActor->ReferenceMap.Empty(); 567 | TestActor->ReferenceMapKey.Empty(); 568 | 569 | const auto BY_PROPERTY = FBlueprintComponentReference::ForProperty(ABCRCachedTestActor::MeshPropertyName); 570 | const auto BY_PATH = FBlueprintComponentReference::ForPath(ABCRCachedTestActor::MeshComponentName); 571 | 572 | //====================================== 573 | PerfRunner_Single<100>{ TestActor, BY_PROPERTY }.Run(); 574 | PerfRunner_Single<1000>{ TestActor, BY_PROPERTY }.Run(); 575 | PerfRunner_Single<10000>{ TestActor, BY_PROPERTY }.Run(); 576 | PerfRunner_Single<100000>{ TestActor, BY_PROPERTY }.Run(); 577 | PerfRunner_Single<1000000>{ TestActor, BY_PROPERTY }.Run(); 578 | //====================================== 579 | PerfRunner_Single<100>{ TestActor, BY_PATH }.Run(); 580 | PerfRunner_Single<1000>{ TestActor, BY_PATH }.Run(); 581 | PerfRunner_Single<10000>{ TestActor, BY_PATH }.Run(); 582 | PerfRunner_Single<100000>{ TestActor, BY_PATH }.Run(); 583 | PerfRunner_Single<1000000>{ TestActor, BY_PATH }.Run(); 584 | //====================================== 585 | PerfRunner_Array<1, 100> { TestActor, BY_PROPERTY }.Run(); 586 | PerfRunner_Array<1, 1000> { TestActor, BY_PROPERTY }.Run(); 587 | PerfRunner_Array<1, 10000> { TestActor, BY_PROPERTY }.Run(); 588 | PerfRunner_Array<1, 100000> { TestActor, BY_PROPERTY }.Run(); 589 | PerfRunner_Array<1, 1000000> { TestActor, BY_PROPERTY }.Run(); 590 | 591 | PerfRunner_Array<10, 100> { TestActor, BY_PROPERTY }.Run(); 592 | PerfRunner_Array<10, 1000> { TestActor, BY_PROPERTY }.Run(); 593 | PerfRunner_Array<10, 10000> { TestActor, BY_PROPERTY }.Run(); 594 | PerfRunner_Array<10, 100000> { TestActor, BY_PROPERTY }.Run(); 595 | PerfRunner_Array<10, 1000000> { TestActor, BY_PROPERTY }.Run(); 596 | 597 | PerfRunner_Array<100, 100> { TestActor, BY_PROPERTY }.Run(); 598 | PerfRunner_Array<100, 1000> { TestActor, BY_PROPERTY }.Run(); 599 | PerfRunner_Array<100, 10000> { TestActor, BY_PROPERTY }.Run(); 600 | PerfRunner_Array<100, 100000> { TestActor, BY_PROPERTY }.Run(); 601 | PerfRunner_Array<100, 1000000> { TestActor, BY_PROPERTY }.Run(); 602 | //====================================== 603 | PerfRunner_Array<1, 100> { TestActor, BY_PATH }.Run(); 604 | PerfRunner_Array<1, 1000> { TestActor, BY_PATH }.Run(); 605 | PerfRunner_Array<1, 10000> { TestActor, BY_PATH }.Run(); 606 | PerfRunner_Array<1, 100000> { TestActor, BY_PATH }.Run(); 607 | PerfRunner_Array<1, 1000000> { TestActor, BY_PATH }.Run(); 608 | 609 | PerfRunner_Array<10, 100> { TestActor, BY_PATH }.Run(); 610 | PerfRunner_Array<10, 1000> { TestActor, BY_PATH }.Run(); 611 | PerfRunner_Array<10, 10000> { TestActor, BY_PATH }.Run(); 612 | PerfRunner_Array<10, 100000> { TestActor, BY_PATH }.Run(); 613 | PerfRunner_Array<10, 1000000> { TestActor, BY_PATH }.Run(); 614 | 615 | PerfRunner_Array<100, 100> { TestActor, BY_PATH }.Run(); 616 | PerfRunner_Array<100, 1000> { TestActor, BY_PATH }.Run(); 617 | PerfRunner_Array<100, 10000> { TestActor, BY_PATH }.Run(); 618 | PerfRunner_Array<100, 100000> { TestActor, BY_PATH }.Run(); 619 | PerfRunner_Array<100, 1000000> { TestActor, BY_PATH }.Run(); 620 | //====================================== 621 | PerfRunner_MapKey<10, 1, 100> { TestActor }.Run(); 622 | PerfRunner_MapKey<10, 1, 1000> { TestActor }.Run(); 623 | PerfRunner_MapKey<10, 1, 10000> { TestActor }.Run(); 624 | PerfRunner_MapKey<10, 1, 100000> { TestActor }.Run(); 625 | PerfRunner_MapKey<10, 1, 1000000> { TestActor }.Run(); 626 | 627 | PerfRunner_MapKey<100, 10, 100> { TestActor }.Run(); 628 | PerfRunner_MapKey<100, 10, 1000> { TestActor }.Run(); 629 | PerfRunner_MapKey<100, 10, 10000> { TestActor }.Run(); 630 | PerfRunner_MapKey<100, 10, 100000> { TestActor }.Run(); 631 | PerfRunner_MapKey<100, 10, 1000000> { TestActor }.Run(); 632 | 633 | PerfRunner_MapKey<100, 50, 100> { TestActor }.Run(); 634 | PerfRunner_MapKey<100, 50, 1000> { TestActor }.Run(); 635 | PerfRunner_MapKey<100, 50, 10000> { TestActor }.Run(); 636 | PerfRunner_MapKey<100, 50, 100000> { TestActor }.Run(); 637 | PerfRunner_MapKey<100, 50, 1000000> { TestActor }.Run(); 638 | 639 | return true; 640 | } 641 | 642 | #endif 643 | --------------------------------------------------------------------------------