// Copyright Epic Games, Inc. All Rights Reserved. #include "StructSerializer.h" #include "UObject/UnrealType.h" #include "UObject/PropertyOptional.h" #include "IStructSerializerBackend.h" /* Internal helpers *****************************************************************************/ namespace StructSerializer { template struct TScriptHelper_InContainer { }; template <> struct TScriptHelper_InContainer { using Type = FScriptArrayHelper_InContainer; }; template <> struct TScriptHelper_InContainer { using Type = FScriptSetHelper_InContainer; }; template <> struct TScriptHelper_InContainer { using Type = FScriptMapHelper_InContainer; }; FStructSerializerState CreateItemState(FScriptArrayHelper& InHelper, FArrayProperty* InArrayProperty, int32 InValidatedInternalElementIndex, const FStructSerializerPolicies& Policies, EStructSerializerStateFlags InFlags) { return FStructSerializerState(InHelper.GetRawPtr(InValidatedInternalElementIndex), InArrayProperty->Inner, InFlags); }; FStructSerializerState CreateItemState(FScriptMapHelper& InHelper, FMapProperty* InMapProperty, int32 InValidatedInternalElementIndex, const FStructSerializerPolicies& Policies, EStructSerializerStateFlags InFlags) { FStructSerializerState State = FStructSerializerState(InHelper.GetPairPtr(InValidatedInternalElementIndex), InMapProperty->ValueProp, InFlags); if (Policies.MapSerialization == EStructSerializerMapPolicies::KeyValuePair) { State.KeyData = InHelper.GetPairPtr(InValidatedInternalElementIndex); State.ValueData = State.KeyData; State.KeyProperty = InMapProperty->KeyProp; } return State; } FStructSerializerState CreateItemState(FScriptSetHelper& InHelper, FSetProperty* InSetProperty, int32 InValidatedInternalElementIndex, const FStructSerializerPolicies& Policies, EStructSerializerStateFlags InFlags) { return FStructSerializerState(InHelper.GetElementPtr(InValidatedInternalElementIndex), InSetProperty->ElementProp, InFlags); } bool IsArrayLike(FProperty* Property, const FStructSerializerPolicies& Policies) { return Property->IsA() || Property->IsA() || (Property->IsA() && Policies.MapSerialization == EStructSerializerMapPolicies::Array); } void BeginIteratable(IStructSerializerBackend& Backend, FStructSerializerState& CurrentState, const FStructSerializerPolicies& Policies) { if (IsArrayLike(CurrentState.ValueProperty, Policies)) { Backend.BeginArray(CurrentState); } else { Backend.BeginStructure(CurrentState); } } void EndIteratable(IStructSerializerBackend& Backend, FStructSerializerState& CurrentState, const FStructSerializerPolicies& Policies) { if (IsArrayLike(CurrentState.ValueProperty, Policies)) { Backend.EndArray(CurrentState); } else { Backend.EndStructure(CurrentState); } } template TArray GenerateStatesForIteratable(FStructSerializerState& CurrentState, const FStructSerializerPolicies& Policies) { using TScriptHelperType = typename TScriptHelper_InContainer::Type; TArray OutStates; IteratablePropertyType* IteratableProperty = CastFieldChecked(CurrentState.ValueProperty); TScriptHelperType ScriptHelper(IteratableProperty, CurrentState.ValueData); const int32 NumOutStates = CurrentState.ElementIndex != INDEX_NONE ? ScriptHelper.Num() : 1; OutStates.Reserve(NumOutStates); // If a specific index is asked only push that one on the stack if (CurrentState.ElementIndex != INDEX_NONE) { if (ScriptHelper.IsValidIndex(CurrentState.ElementIndex)) { OutStates.Add(CreateItemState(ScriptHelper, IteratableProperty, CurrentState.ElementIndex, Policies, EStructSerializerStateFlags::WritingContainerElement)); } } else { // push values on stack (in reverse order) for (int32 Index = ScriptHelper.Num() - 1; Index >= 0; --Index) { if (ScriptHelper.IsValidIndex(Index)) { OutStates.Push(CreateItemState(ScriptHelper, IteratableProperty, Index, Policies, EStructSerializerStateFlags::None)); } } } return OutStates; } template void SerializeIterable(FStructSerializerState& CurrentState, TArray& StateStack, IStructSerializerBackend& Backend, const FStructSerializerPolicies& Policies) { if (!CurrentState.HasBeenProcessed) { // Only begin the iteratable if we are not serializing a single element. if (!EnumHasAnyFlags(CurrentState.StateFlags, EStructSerializerStateFlags::WritingContainerElement)) { BeginIteratable(Backend, CurrentState, Policies); } if (!(CurrentState.ValueProperty->IsA() && Backend.WritePODArray(CurrentState))) { CurrentState.HasBeenProcessed = true; StateStack.Push(CurrentState); // States are pushed on the stack in reverse order. for (FStructSerializerState& State : GenerateStatesForIteratable(CurrentState, Policies)) { StateStack.Push(MoveTemp(State)); } } } else if (!EnumHasAnyFlags(CurrentState.StateFlags, EStructSerializerStateFlags::WritingContainerElement)) { //Close iteratable only if we were not targeting a single element EndIteratable(Backend, CurrentState, Policies); } } void SerializeOptional(FStructSerializerState& CurrentState, TArray& StateStack, IStructSerializerBackend& Backend) { if (!CurrentState.HasBeenProcessed) { Backend.BeginStructure(CurrentState); CurrentState.HasBeenProcessed = true; StateStack.Push(CurrentState); const FOptionalProperty* OptionalProperty = CastFieldChecked(CurrentState.ValueProperty); FProperty* InnerProperty = OptionalProperty->GetValueProperty(); const void* ValueData = CurrentState.ValueProperty->ContainerPtrToValuePtr(CurrentState.ValueData); if (const void* InnerValue = OptionalProperty->GetValuePointerForReadIfSet(ValueData)) { FStructSerializerState NewState; NewState.ValueData = InnerValue; NewState.ValueProperty = InnerProperty; NewState.FieldType = InnerProperty->GetClass(); StateStack.Add(NewState); } } else { Backend.EndStructure(CurrentState); } } void SerializeStaticArray(FStructSerializerState& CurrentState, IStructSerializerBackend& Backend) { if (CurrentState.ElementIndex != INDEX_NONE) { if (CurrentState.ElementIndex < CurrentState.ValueProperty->ArrayDim) { Backend.WriteProperty(CurrentState, CurrentState.ElementIndex); } } else { Backend.BeginArray(CurrentState); for (int32 ArrayIndex = 0; ArrayIndex < CurrentState.ValueProperty->ArrayDim; ++ArrayIndex) { Backend.WriteProperty(CurrentState, ArrayIndex); } Backend.EndArray(CurrentState); } } TArray GenerateStructureStates(FStructSerializerState& CurrentState, const FStructSerializerPolicies& Policies) { TArray NewStates; const void* ValueData = CurrentState.ValueData; if (CurrentState.ValueType) { if (CurrentState.ValueProperty) { FFieldVariant Outer = CurrentState.ValueProperty->GetOwnerVariant(); if ((Outer.ToField() == nullptr) || (Outer.ToField()->GetClass() != FArrayProperty::StaticClass())) { const int32 ContainerAddressIndex = CurrentState.ElementIndex != INDEX_NONE ? CurrentState.ElementIndex : 0; ValueData = CurrentState.ValueProperty->ContainerPtrToValuePtr(CurrentState.ValueData, ContainerAddressIndex); } } for (TFieldIterator It(CurrentState.ValueType, EFieldIteratorFlags::IncludeSuper); It; ++It) { // Skip property if the filter function is set and rejects it. if (Policies.PropertyFilter && !Policies.PropertyFilter(*It, CurrentState.ValueProperty)) { continue; } FStructSerializerState NewState; { NewState.ValueData = ValueData; NewState.ValueProperty = *It; NewState.FieldType = It->GetClass(); } NewStates.Add(MoveTemp(NewState)); } } return NewStates; } TArray GenerateStructureArrayStates(FStructSerializerState& CurrentState, const FStructSerializerPolicies& Policies) { TArray NewStates; // push elements on stack (in reverse order) for (int32 Index = CurrentState.ValueProperty->ArrayDim - 1; Index >= 0; --Index) { FStructSerializerState NewState; { NewState.ValueData = CurrentState.ValueData; NewState.ValueProperty = CurrentState.ValueProperty; NewState.ElementIndex = Index; } NewStates.Add(MoveTemp(NewState)); } return NewStates; } UStruct* GetValueType(const FStructSerializerState& State) { UStruct* ValueType = nullptr; if (State.ValueProperty != nullptr) { //Get the type to iterate over the fields if (FStructProperty* StructProperty = CastField(State.ValueProperty)) { ValueType = StructProperty->Struct; } else if (FObjectPropertyBase* ObjectProperty = CastField(State.ValueProperty)) { ValueType = ObjectProperty->PropertyClass; } } return ValueType; } void SerializeStructInStaticArray(FStructSerializerState& CurrentState, TArray& StateStack, IStructSerializerBackend& Backend, const FStructSerializerPolicies& Policies) { if (!CurrentState.HasBeenProcessed) { //Push ourself to close the array CurrentState.HasBeenProcessed = true; StateStack.Push(CurrentState); Backend.BeginArray(CurrentState); for (FStructSerializerState& NewState : GenerateStructureArrayStates(CurrentState, Policies)) { StateStack.Push(MoveTemp(NewState)); } } else { Backend.EndArray(CurrentState); } } void SerializeStruct(FStructSerializerState& CurrentState, TArray& StateStack, IStructSerializerBackend& Backend, const FStructSerializerPolicies& Policies) { if (CurrentState.ValueProperty && CurrentState.ValueProperty->ArrayDim > 1 && CurrentState.ElementIndex == INDEX_NONE) { SerializeStructInStaticArray(CurrentState, StateStack, Backend, Policies); } else { if (!CurrentState.HasBeenProcessed) { if (UStruct* ValueType = GetValueType(CurrentState)) { CurrentState.ValueType = ValueType; } if (CurrentState.ValueProperty) { Backend.BeginStructure(CurrentState); } CurrentState.HasBeenProcessed = true; StateStack.Push(CurrentState); TArray NewStates = GenerateStructureStates(CurrentState, Policies); // push child properties on stack (in reverse order) for (int32 Index = NewStates.Num() - 1; Index >= 0; --Index) { StateStack.Push(MoveTemp(NewStates[Index])); } } else { if (CurrentState.ValueProperty) { Backend.EndStructure(CurrentState); } } } } void Serialize(FStructSerializerState InitialState, IStructSerializerBackend& Backend, const FStructSerializerPolicies& Policies) { TArray StateStack; StateStack.Push(MoveTemp(InitialState)); // Always encompass the element in an object Backend.BeginStructure(FStructSerializerState()); // process state stack while (StateStack.Num() > 0) { FStructSerializerState CurrentState = StateStack.Pop(EAllowShrinking::No); // Structures if (!CurrentState.ValueProperty || CastField(CurrentState.ValueProperty)) { SerializeStruct(CurrentState, StateStack, Backend, Policies); } // Dynamic arrays else if (CastField(CurrentState.ValueProperty)) { SerializeIterable(CurrentState, StateStack, Backend, Policies); } // Maps else if (CastField(CurrentState.ValueProperty)) { SerializeIterable(CurrentState, StateStack, Backend, Policies); } // Sets else if (CastField(CurrentState.ValueProperty)) { SerializeIterable(CurrentState, StateStack, Backend, Policies); } // Optionals else if (CastField(CurrentState.ValueProperty)) { SerializeOptional(CurrentState, StateStack, Backend); } // Static arrays else if (CurrentState.ValueProperty->ArrayDim > 1) { SerializeStaticArray(CurrentState, Backend); } // All other properties else { Backend.WriteProperty(CurrentState); } } Backend.EndStructure(FStructSerializerState()); } } /* FStructSerializer static interface *****************************************************************************/ void FStructSerializer::Serialize(const void* Struct, UStruct& TypeInfo, IStructSerializerBackend& Backend, const FStructSerializerPolicies& Policies) { check(Struct != nullptr); FStructSerializerState NewState; { NewState.ValueData = Struct; NewState.ValueType = &TypeInfo; } StructSerializer::Serialize(MoveTemp(NewState), Backend, Policies); } void FStructSerializer::SerializeElement(const void* Address, FProperty* Property, int32 ElementIndex, IStructSerializerBackend& Backend, const FStructSerializerPolicies& Policies) { check(Address != nullptr); check(Property != nullptr); //Initial state with the desired property info FStructSerializerState NewState; { NewState.ValueData = Address; NewState.ElementIndex = ElementIndex; NewState.StateFlags = ElementIndex != INDEX_NONE ? EStructSerializerStateFlags::WritingContainerElement : EStructSerializerStateFlags::None; NewState.ValueProperty = Property; NewState.FieldType = Property->GetClass(); } if (Policies.MapSerialization == EStructSerializerMapPolicies::KeyValuePair && ElementIndex != INDEX_NONE) { UE_LOG(LogSerialization, Warning, TEXT("SerializeElement skipped map property %s. Only supports maps as array."), *Property->GetFName().ToString()); return; } StructSerializer::Serialize(MoveTemp(NewState), Backend, Policies); }