// Copyright Epic Games, Inc. All Rights Reserved. #include "ComponentVisualizer.h" #include "ActorEditorUtils.h" #include "Components/ChildActorComponent.h" #include "Containers/ContainerAllocationPolicies.h" #include "GameFramework/Actor.h" #include "HAL/PlatformCrt.h" #include "Misc/AssertionMacros.h" #include "Templates/Casts.h" #include "Templates/UnrealTemplate.h" #include "UObject/Class.h" #include "UObject/Field.h" #include "UObject/Object.h" IMPLEMENT_HIT_PROXY(HComponentVisProxy, HHitProxy); namespace ComponentVisualizers { static bool GAutoSelectComponentWithPointSelection = true; static FAutoConsoleVariableRef CVarAutoSelectComponentWithPointSelection( TEXT("Editor.ComponentVisualizer.AutoSelectComponent"), GAutoSelectComponentWithPointSelection, TEXT("Automatically adds the visualized component to the selection set if " "available when visualization handles a click (e.g. select spline component when a point is selected on the spline).") ); }; // namespace ComponentVisualizers static FPropertyNameAndIndex GetActorPropertyNameAndIndexForComponent(const AActor* Actor, const UActorComponent* Component) { if (Actor != nullptr && Component != nullptr) { // Iterate over UObject* fields of this actor UClass* ActorClass = Actor->GetClass(); for (TFieldIterator It(ActorClass); It; ++It) { // See if this property points to the component in question FObjectProperty* ObjectProp = *It; for (int32 Index = 0; Index < ObjectProp->ArrayDim; ++Index) { UObject* Object = ObjectProp->GetObjectPropertyValue(ObjectProp->ContainerPtrToValuePtr(Actor, Index)); if (Object == Component) { // It does! Return this name return FPropertyNameAndIndex(ObjectProp->GetFName(), Index); } } } // If nothing found, look in TArray fields for (TFieldIterator It(ActorClass); It; ++It) { FArrayProperty* ArrayProp = *It; if (FObjectProperty* InnerProp = CastField(It->Inner)) { FScriptArrayHelper ArrayHelper(ArrayProp, ArrayProp->ContainerPtrToValuePtr(Actor)); for (int32 Index = 0; Index < ArrayHelper.Num(); ++Index) { UObject* Object = InnerProp->GetObjectPropertyValue(ArrayHelper.GetRawPtr(Index)); if (Object == Component) { return FPropertyNameAndIndex(ArrayProp->GetFName(), Index); } } } } } return FPropertyNameAndIndex(); } void FComponentPropertyPath::Set(const UActorComponent* Component) { // Determine which property on the component's actor owner references the component. AActor* Actor = Component->GetOwner(); // If such a property were found, build a chain of such properties, recursing up actor parent components, // until we reach the top UChildActorComponent* ParentComponent = Actor->GetParentComponent(); if (ParentComponent) { Set(ParentComponent); // recurse to next parent } else { // If there are no further parents, store this one (the outermost) ParentOwningActor = Actor; // We have successfully arrived at the top of the actor/component tree, and have a valid property chain. // Hence we don't need to cache the current component ptr as a last resort. LastResortComponentPtr = nullptr; } // If a last resort component ptr has been set, no need to build the property chain if (LastResortComponentPtr.IsValid()) { return; } FPropertyNameAndIndex PropertyNameAndIndex = GetActorPropertyNameAndIndexForComponent(Actor, Component); if (PropertyNameAndIndex.IsValid()) { // If we found a property, add it to the chain after the recursion, so they are added outermost-first. PropertyChain.Add(PropertyNameAndIndex); } else { // If no such property were found, we set a "last resort" weak ptr to the component itself and get rid of the property chain. // This is not preferable as we can't recuperate the component if its address changes (e.g. on hot reload or BP reconstruction). // However it is valid to have an owned component without a UPROPERTY reference, so we need to handle this case. LastResortComponentPtr = const_cast(Component); PropertyChain.Empty(); } } UActorComponent* FComponentPropertyPath::GetComponent() const { // If there's a valid "last resort" component ptr, use this. if (LastResortComponentPtr.IsValid()) { return LastResortComponentPtr.Get(); } UActorComponent* Result = nullptr; const AActor* Actor = ParentOwningActor.Get(); if (Actor) { int32 Level = 0; for (const FPropertyNameAndIndex& PropertyNameAndIndex : PropertyChain) { Result = nullptr; if (PropertyNameAndIndex.IsValid()) { FName PropertyName = PropertyNameAndIndex.Name; int32 PropertyIndex = PropertyNameAndIndex.Index; UClass* ActorClass = Actor->GetClass(); check(ActorClass); FProperty* Prop = FindFProperty(ActorClass, PropertyName); if (FObjectProperty* ObjectProp = CastField(Prop)) { UObject* Object = ObjectProp->GetObjectPropertyValue(ObjectProp->ContainerPtrToValuePtr(Actor, PropertyIndex)); Result = Cast(Object); } else if (FArrayProperty* ArrayProp = CastField(Prop)) { if (FObjectProperty* InnerProp = CastField(ArrayProp->Inner)) { FScriptArrayHelper ArrayHelper(ArrayProp, ArrayProp->ContainerPtrToValuePtr(Actor)); if (ArrayHelper.IsValidIndex(PropertyIndex)) { UObject* Object = InnerProp->GetObjectPropertyValue(ArrayHelper.GetRawPtr(PropertyIndex)); Result = Cast(Object); } } } } Level++; if (Level < PropertyChain.Num()) { UChildActorComponent* ChildActorComponent = Cast(Result); if (ChildActorComponent == nullptr) { break; } Actor = ChildActorComponent->GetChildActor(); if (Actor == nullptr) { break; } } } } return Result; } bool FComponentPropertyPath::IsValid() const { // If there's a valid "last resort" component, this will always be valid. if (LastResortComponentPtr.IsValid()) { return true; } if (!ParentOwningActor.IsValid()) { return false; } for (const FPropertyNameAndIndex& PropertyNameAndIndex : PropertyChain) { if (!PropertyNameAndIndex.IsValid()) { return false; } } return true; } bool FComponentVisualizer::ShouldAutoSelectElementOnHandleClick() const { return ComponentVisualizers::GAutoSelectComponentWithPointSelection; } void FComponentVisualizer::NotifyPropertyModified(UActorComponent* Component, FProperty* Property, EPropertyChangeType::Type PropertyChangeType) { TArray Properties; Properties.Add(Property); NotifyPropertiesModified(Component, Properties, PropertyChangeType); } void FComponentVisualizer::NotifyPropertiesModified(UActorComponent* Component, const TArray& Properties, EPropertyChangeType::Type PropertyChangeType) { if (Component == nullptr) { return; } for (FProperty* Property : Properties) { FPropertyChangedEvent PropertyChangedEvent(Property, PropertyChangeType); Component->PostEditChangeProperty(PropertyChangedEvent); } AActor* Owner = Component->GetOwner(); if (Owner && FActorEditorUtils::IsAPreviewOrInactiveActor(Owner)) { // The component belongs to the preview actor in the BP editor, so we need to propagate the property change to the archetype. // Before this, we exploit the fact that the archetype and the preview actor have the old and new value of the property, respectively. // So we can go through all archetype instances, and if they hold the (old) archetype value, update it to the new value. // Get archetype UActorComponent* Archetype = Cast(Component->GetArchetype()); check(Archetype); // Get all archetype instances (the preview actor passed in should be amongst them) TArray ArchetypeInstances; Archetype->GetArchetypeInstances(ArchetypeInstances); check(ArchetypeInstances.Contains(Component)); // Identify which of the modified properties are at their default values in the archetype instances, // and thus need the new value to be propagated to them struct FInstanceDefaultProperties { UActorComponent* ArchetypeInstance; TArray> Properties; }; TArray InstanceDefaultProperties; InstanceDefaultProperties.Reserve(ArchetypeInstances.Num()); for (UObject* ArchetypeInstance : ArchetypeInstances) { UActorComponent* InstanceComp = Cast(ArchetypeInstance); if (InstanceComp != Component) { FInstanceDefaultProperties Entry; for (FProperty* Property : Properties) { uint8* ArchetypePtr = Property->ContainerPtrToValuePtr(Archetype); uint8* InstancePtr = Property->ContainerPtrToValuePtr(InstanceComp); if (Property->Identical(ArchetypePtr, InstancePtr)) { Entry.Properties.Add(Property); } } if (Entry.Properties.Num() > 0) { Entry.ArchetypeInstance = InstanceComp; InstanceDefaultProperties.Add(MoveTemp(Entry)); } } } // Propagate all modified properties to the archetype Archetype->SetFlags(RF_Transactional); Archetype->Modify(); if (Archetype->GetOwner()) { Archetype->GetOwner()->Modify(); } for (FProperty* Property : Properties) { uint8* ArchetypePtr = Property->ContainerPtrToValuePtr(Archetype); uint8* PreviewPtr = Property->ContainerPtrToValuePtr(Component); Property->CopyCompleteValue(ArchetypePtr, PreviewPtr); FPropertyChangedEvent PropertyChangedEvent(Property); Archetype->PostEditChangeProperty(PropertyChangedEvent); } // Apply changes to each archetype instance for (const auto& Instance : InstanceDefaultProperties) { Instance.ArchetypeInstance->SetFlags(RF_Transactional); Instance.ArchetypeInstance->Modify(); AActor* InstanceOwner = Instance.ArchetypeInstance->GetOwner(); if (InstanceOwner) { InstanceOwner->Modify(); } for (FProperty* Property : Instance.Properties) { uint8* InstancePtr = Property->ContainerPtrToValuePtr(Instance.ArchetypeInstance); uint8* PreviewPtr = Property->ContainerPtrToValuePtr(Component); Property->CopyCompleteValue(InstancePtr, PreviewPtr); FPropertyChangedEvent PropertyChangedEvent(Property); Instance.ArchetypeInstance->PostEditChangeProperty(PropertyChangedEvent); } // Rerun construction script on instance if (InstanceOwner) { InstanceOwner->PostEditMove(PropertyChangeType == EPropertyChangeType::ValueSet); } } // Rerun construction script on preview actor if (Owner) { Owner->PostEditMove(PropertyChangeType == EPropertyChangeType::ValueSet); } } }