Files
UnrealEngine/Engine/Source/Runtime/MovieScene/Private/EntitySystem/MovieScenePropertyRegistry.cpp
2025-05-18 13:04:45 +08:00

228 lines
8.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EntitySystem/MovieScenePropertyRegistry.h"
#include "MovieSceneFwd.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "EntitySystem/MovieScenePropertyBinding.h"
#include "EntitySystem/MovieSceneInitialValueSystem.h"
#include "MovieSceneCommonHelpers.h"
namespace UE
{
namespace MovieScene
{
struct FPropertyAndAddress
{
FProperty* Property = nullptr;
uint8* PropertyAddress = nullptr;
};
/**
* Find a nested property and address from the specified path of the form StructProp.Struct.Inner.
* Any symbols other than identifiers and dots are not supported (or even checked for).
*/
template<typename CharType>
FPropertyAndAddress FindPropertyFromNestedPath(UStruct* Struct, void* Container, TStringView<CharType> InPath)
{
// Find the next dot in the path
int32 DotIndex = InPath.Len();
const bool bHasTail = InPath.FindChar('.', DotIndex);
// Look up our property name using FNAME_Find. If no name exists with this name, a property cannot exist for it.
TStringView<CharType> Head = bHasTail ? InPath.Left(DotIndex) : InPath;
FName PropertyName(Head.Len(), Head.GetData(), FNAME_Find);
if (PropertyName.IsNone())
{
return FPropertyAndAddress{};
}
// Find a property for the head
FProperty* Property = Struct->FindPropertyByName(PropertyName);
if (!Property)
{
return FPropertyAndAddress{};
}
uint8* PropertyAddress = Property->ContainerPtrToValuePtr<uint8>(Container);
// If we have more properties to look up, start again from the tail path and the current property address
if (bHasTail)
{
FStructProperty* StructProperty = CastField<FStructProperty>(Property);
if (!StructProperty || !StructProperty->Struct)
{
return FPropertyAndAddress{};
}
TStringView<CharType> Tail = InPath.RightChop(DotIndex+1);
return FindPropertyFromNestedPath(StructProperty->Struct, PropertyAddress, Tail);
}
return FPropertyAndAddress{ Property, PropertyAddress };
}
FPropertyAndAddress FindPropertyFromPath(UClass* ObjectClass, const FMovieScenePropertyBinding& PropertyBinding)
{
if (PropertyBinding.PropertyName == PropertyBinding.PropertyPath)
{
// Simple case - just a property name
FProperty* Property = ObjectClass->FindPropertyByName(PropertyBinding.PropertyName);
if (Property)
{
UObject* DefaultObject = ObjectClass->GetDefaultObject();
uint8* PropertyAddress = Property->ContainerPtrToValuePtr<uint8>(DefaultObject);
return FPropertyAndAddress{ Property, PropertyAddress };
}
}
else
{
// Lookup the property name using a string view to avoid allocation.
// Most properties will be ansi strings
const FNameEntry* NameEntry = PropertyBinding.PropertyPath.GetComparisonNameEntry();
if (NameEntry)
{
if (NameEntry->IsWide())
{
TWideStringBuilder<128> Path;
NameEntry->AppendNameToString(Path);
return FindPropertyFromNestedPath(ObjectClass, ObjectClass->GetDefaultObject(), Path.ToView());
}
else
{
TAnsiStringBuilder<128> Path;
NameEntry->AppendAnsiNameToString(Path);
return FindPropertyFromNestedPath(ObjectClass, ObjectClass->GetDefaultObject(), Path.ToView());
}
}
}
return FPropertyAndAddress{};
}
TOptional<uint16> ComputeFastPropertyPtrOffset(UClass* ObjectClass, const FMovieScenePropertyBinding& PropertyBinding)
{
using namespace UE::MovieScene;
FPropertyAndAddress PropertyAndAddress = FindPropertyFromPath(ObjectClass, PropertyBinding);
const bool bFoundSetter = (!PropertyAndAddress.Property || PropertyAndAddress.Property->HasSetter());
if (bFoundSetter)
{
return TOptional<uint16>();
}
if (!PropertyAndAddress.Property)
{
return TOptional<uint16>();
}
// For object properties, we can't use the fast path, as reference tracking may be necessary
if (FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(PropertyAndAddress.Property))
{
return TOptional<uint16>();
}
// @todo: Constructing FNames from strings is _very_ costly and we really shouldn't be doing this at runtime.
// This is a little better now we use a string builder and an FNAME_Find, but it's still not ideal
TStringBuilder<128> SetterName;
SetterName.Append(TEXT("Set"));
PropertyBinding.PropertyName.AppendString(SetterName);
FName SetterFunctionName(SetterName.ToString(), FNAME_Find);
if (SetterFunctionName.IsNone() || ObjectClass->FindFunctionByName(SetterFunctionName) == nullptr)
{
if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(PropertyAndAddress.Property))
{
// bitfield booleans potentially have an additional byte offset.
// In practice this is always 0 because the property internal offset itself is incremented, but this is here for completeness
PropertyAndAddress.PropertyAddress += BoolProperty->GetByteOffset();
}
UObject* DefaultObject = ObjectClass->GetDefaultObject();
int32 PropertyOffset = PropertyAndAddress.PropertyAddress - reinterpret_cast<uint8*>(DefaultObject);
if (PropertyOffset >= 0 && PropertyOffset < int32(uint16(0xFFFF)))
{
return uint16(PropertyOffset);
}
}
return TOptional<uint16>();
}
TOptional<FResolvedFastProperty> FPropertyRegistry::ResolveFastProperty(UObject* Object, const FMovieScenePropertyBinding& PropertyBinding, FCustomAccessorView CustomAccessors)
{
UClass* Class = Object->GetClass();
if (CustomAccessors.Num() != 0)
{
const int32 CustomPropertyIndex = CustomAccessors.FindCustomAccessorIndex(Class, PropertyBinding.PropertyPath);
if (CustomPropertyIndex != INDEX_NONE)
{
check(CustomPropertyIndex < MAX_uint16);
// This property has a custom property accessor that can apply properties through a static function ptr.
// Just add the function ptrs to the property entity so they can be called directly
return FResolvedFastProperty(TInPlaceType<FCustomPropertyIndex>(), FCustomPropertyIndex{ static_cast<uint16>(CustomPropertyIndex) });
}
}
if (PropertyBinding.CanUseClassLookup())
{
TOptional<uint16> FastPtrOffset = ComputeFastPropertyPtrOffset(Class, PropertyBinding);
if (FastPtrOffset.IsSet())
{
// This property/object combination has no custom setter function and a constant property offset from the base ptr for all instances of the object.
return FResolvedFastProperty(TInPlaceType<uint16>(), FastPtrOffset.GetValue());
}
}
return TOptional<FResolvedFastProperty>();
}
TOptional<FResolvedProperty> FPropertyRegistry::ResolveProperty(UObject* Object, const FMovieScenePropertyBinding& PropertyBinding, FCustomAccessorView CustomAccessors)
{
TOptional< FResolvedFastProperty > FastProperty = FPropertyRegistry::ResolveFastProperty(Object, PropertyBinding, CustomAccessors);
if (FastProperty.IsSet())
{
if (const FCustomPropertyIndex* CustomIndex = FastProperty->TryGet<FCustomPropertyIndex>())
{
return FResolvedProperty(TInPlaceType<FCustomPropertyIndex>(), *CustomIndex);
}
return FResolvedProperty(TInPlaceType<uint16>(), FastProperty->Get<uint16>());
}
// None of the above optimized paths can apply to this property (probably because it has a setter function or because it is within a compound property), so we must use the slow property bindings
TSharedPtr<FTrackInstancePropertyBindings> SlowBindings = MakeShared<FTrackInstancePropertyBindings>(PropertyBinding.PropertyName, PropertyBinding.PropertyPath.ToString());
if (!SlowBindings->HasValidBinding(*Object))
{
UE_LOG(LogMovieSceneECS, Warning, TEXT("Unable to resolve property '%s' from '%s' instance '%s'"), *PropertyBinding.PropertyPath.ToString(), *Object->GetClass()->GetName(), *Object->GetName());
return TOptional<FResolvedProperty>();
}
return FResolvedProperty(TInPlaceType<TSharedPtr<FTrackInstancePropertyBindings>>(), SlowBindings);
}
void FPropertyDefinition::SetupInitialValueProcessor() const
{
if (InitialValueType)
{
TSharedPtr<IInitialValueProcessor> InitialValueProcessor = Handler->MakeInitialValueProcessor(*this);
if (InitialValueProcessor)
{
FEntityComponentFilter Filter;
InitialValueProcessor->PopulateFilter(Filter);
UMovieSceneInitialValueSystem::RegisterProcessor(InitialValueType, MoveTemp(InitialValueProcessor), MoveTemp(Filter));
}
}
}
} // namespace MovieScene
} // namespace UE