301 lines
10 KiB
C
301 lines
10 KiB
C
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
#pragma once
|
||
|
|
|
||
|
|
#include "CoreMinimal.h"
|
||
|
|
#include "UObject/ObjectMacros.h"
|
||
|
|
#include "UObject/Object.h"
|
||
|
|
#include "UObject/Class.h"
|
||
|
|
#include "UObject/CoreNet.h"
|
||
|
|
#include "UObject/UnrealType.h"
|
||
|
|
#include "UObject/Package.h"
|
||
|
|
#include "UObject/PropertyPortFlags.h"
|
||
|
|
|
||
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogProperty, Log, All);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Advances the character pointer past any spaces or tabs.
|
||
|
|
*
|
||
|
|
* @param Str the buffer to remove whitespace from
|
||
|
|
*/
|
||
|
|
static void SkipWhitespace(const TCHAR*& Str)
|
||
|
|
{
|
||
|
|
while (FChar::IsWhitespace(*Str))
|
||
|
|
{
|
||
|
|
Str++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Determine whether the editable properties of CompA and CompB are identical. Used
|
||
|
|
* to determine whether the instanced object has been modified in the editor.
|
||
|
|
*
|
||
|
|
* @param ObjectA the first Instanced Object to compare
|
||
|
|
* @param ObjectB the second Instanced Object to compare
|
||
|
|
* @param PortFlags flags for modifying the criteria used for determining whether the components are identical
|
||
|
|
*
|
||
|
|
* @return true if the values of all of the editable properties of CompA match the values in CompB
|
||
|
|
*/
|
||
|
|
static bool AreInstancedObjectsIdentical(UObject* ObjectA, UObject* ObjectB, uint32 PortFlags)
|
||
|
|
{
|
||
|
|
check(ObjectA && ObjectB);
|
||
|
|
bool Result = true;
|
||
|
|
|
||
|
|
// we check for recursive comparison here...if we have encountered this pair before on the call stack, the graphs are isomorphic
|
||
|
|
struct FRecursionCheck
|
||
|
|
{
|
||
|
|
UObject* ObjectA;
|
||
|
|
UObject* ObjectB;
|
||
|
|
uint32 PortFlags;
|
||
|
|
|
||
|
|
bool operator==(const FRecursionCheck& Other) const
|
||
|
|
{
|
||
|
|
return ObjectA == Other.ObjectA && ObjectB == Other.ObjectB && PortFlags == Other.PortFlags;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
static TArray<FRecursionCheck> RecursionCheck;
|
||
|
|
FRecursionCheck Test;
|
||
|
|
Test.ObjectA = ObjectA;
|
||
|
|
Test.ObjectB = ObjectB;
|
||
|
|
Test.PortFlags = PortFlags;
|
||
|
|
if (!RecursionCheck.Contains(Test))
|
||
|
|
{
|
||
|
|
RecursionCheck.Push(Test);
|
||
|
|
for (FProperty* Prop = ObjectA->GetClass()->PropertyLink; Prop && Result; Prop = Prop->PropertyLinkNext)
|
||
|
|
{
|
||
|
|
// only the properties that could have been modified in the editor should be compared
|
||
|
|
// (skipping the name and archetype properties, since name will almost always be different)
|
||
|
|
bool bConsiderProperty;
|
||
|
|
if ((PortFlags & PPF_Copy) == 0)
|
||
|
|
{
|
||
|
|
bConsiderProperty = Prop->ShouldDuplicateValue();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
bConsiderProperty = (Prop->PropertyFlags & CPF_Edit) != 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bConsiderProperty)
|
||
|
|
{
|
||
|
|
for (int32 i = 0; i < Prop->ArrayDim && Result; i++)
|
||
|
|
{
|
||
|
|
if (!Prop->Identical_InContainer(ObjectA, ObjectB, i, PortFlags))
|
||
|
|
{
|
||
|
|
Result = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (Result)
|
||
|
|
{
|
||
|
|
// Allow the component to compare its native/ intrinsic properties.
|
||
|
|
Result = ObjectA->AreNativePropertiesIdenticalTo(ObjectB);
|
||
|
|
}
|
||
|
|
RecursionCheck.Pop();
|
||
|
|
}
|
||
|
|
return Result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Helper struct for serializing index deltas
|
||
|
|
* -Serialize delta index as a packed int (Hope to get 1 byte per index)
|
||
|
|
* -Serialize 0 delta to signify 'no more' (INDEX_NONE would take 5 bytes in packed format)
|
||
|
|
*/
|
||
|
|
struct FDeltaIndexHelper
|
||
|
|
{
|
||
|
|
FDeltaIndexHelper()
|
||
|
|
{
|
||
|
|
LastIndex = -1; // Start at -1 so index 0 can be serialized as delta=1 (so that 0 can be reserved for 'no more')
|
||
|
|
LastIndexFull = -1; // Separate index for full state since it will never be rolled back
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Serialize Index as delta from previous index. Return false if we should stop */
|
||
|
|
bool SerializeNext(FArchive& Ar, int32& Index)
|
||
|
|
{
|
||
|
|
NET_CHECKSUM(Ar);
|
||
|
|
if (Ar.IsSaving())
|
||
|
|
{
|
||
|
|
uint32 Delta = Index - LastIndex;
|
||
|
|
Ar.SerializeIntPacked(Delta);
|
||
|
|
LastIndex = Index;
|
||
|
|
LastIndexFull = Index;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
uint32 Delta = 0;
|
||
|
|
Ar.SerializeIntPacked(Delta);
|
||
|
|
Index = (Delta == 0 ? INDEX_NONE : LastIndex + Delta);
|
||
|
|
LastIndex = Index;
|
||
|
|
LastIndexFull = Index;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (Index != INDEX_NONE && !Ar.IsError());
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Helper for NetSerializeItemDelta which has full/partial/old archives. Wont auto-advance LastIndex, must call Increment after this */
|
||
|
|
void SerializeNext(FArchive& OutBunch, FArchive& OutFull, int32 Index)
|
||
|
|
{
|
||
|
|
NET_CHECKSUM(OutBunch);
|
||
|
|
NET_CHECKSUM(OutFull);
|
||
|
|
|
||
|
|
// Full state
|
||
|
|
uint32 DeltaFull = Index - LastIndexFull;
|
||
|
|
OutFull.SerializeIntPacked(DeltaFull);
|
||
|
|
LastIndexFull = Index;
|
||
|
|
|
||
|
|
// Delta State
|
||
|
|
uint32 Delta = Index - LastIndex;
|
||
|
|
OutBunch.SerializeIntPacked(Delta);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Sets LastIndex for delta state. Must be called if using SerializeNet(OutBunch, Outfull, Old, Index) */
|
||
|
|
void Increment(int32 NewIndex)
|
||
|
|
{
|
||
|
|
LastIndex = NewIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Serialize early end (0) */
|
||
|
|
void SerializeEarlyEnd(FArchive& Ar)
|
||
|
|
{
|
||
|
|
NET_CHECKSUM(Ar);
|
||
|
|
|
||
|
|
uint32 End = 0;
|
||
|
|
Ar.SerializeIntPacked(End);
|
||
|
|
}
|
||
|
|
|
||
|
|
int32 LastIndex;
|
||
|
|
int32 LastIndexFull;
|
||
|
|
};
|
||
|
|
|
||
|
|
namespace DelegatePropertyTools
|
||
|
|
{
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Imports a single-cast delegate as "object.function", or "function" (self is object) from a text buffer
|
||
|
|
*
|
||
|
|
* @param Delegate The delegate to import data into
|
||
|
|
* @param SignatureFunction The delegate property's signature function, used to validate parameters and such
|
||
|
|
* @param Buffer The text buffer to import from
|
||
|
|
* @param Parent The property object's parent object
|
||
|
|
* @param ErrorText Output device for emitting errors and warnings
|
||
|
|
*
|
||
|
|
* @return The adjusted text buffer pointer on success, or NULL on failure
|
||
|
|
*/
|
||
|
|
static const TCHAR* ImportDelegateFromText(FScriptDelegate& Delegate, const UFunction* SignatureFunction, const TCHAR* Buffer, UObject* Parent, FOutputDevice* ErrorText)
|
||
|
|
{
|
||
|
|
SkipWhitespace(Buffer);
|
||
|
|
|
||
|
|
TCHAR ObjName[NAME_SIZE];
|
||
|
|
TCHAR FuncName[NAME_SIZE];
|
||
|
|
// Get object name
|
||
|
|
int32 i;
|
||
|
|
while (*Buffer == TEXT('('))
|
||
|
|
{
|
||
|
|
++Buffer;
|
||
|
|
}
|
||
|
|
for (i = 0; *Buffer && *Buffer != TCHAR('.') && *Buffer != TCHAR(')') && *Buffer != TCHAR(','); Buffer++)
|
||
|
|
{
|
||
|
|
ObjName[i++] = *Buffer;
|
||
|
|
}
|
||
|
|
ObjName[i] = TCHAR('\0');
|
||
|
|
UClass* Cls = nullptr;
|
||
|
|
UObject* Object = nullptr;
|
||
|
|
// Get function name
|
||
|
|
if (*Buffer == TCHAR('.'))
|
||
|
|
{
|
||
|
|
Buffer++;
|
||
|
|
for (i = 0; *Buffer && *Buffer != TCHAR(')') && *Buffer != TCHAR(','); Buffer++)
|
||
|
|
{
|
||
|
|
FuncName[i++] = *Buffer;
|
||
|
|
}
|
||
|
|
FuncName[i] = '\0';
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// if there's no dot, then a function name was specified without any object qualifier
|
||
|
|
// if we're importing for a subobject, assume the parent object as the source
|
||
|
|
// otherwise, assume Parent for the source
|
||
|
|
// if neither are valid, then the importing fails
|
||
|
|
if (Parent == nullptr)
|
||
|
|
{
|
||
|
|
ErrorText->Logf(ELogVerbosity::Warning, TEXT("Cannot import unqualified delegate name; no object to search"));
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
// since we don't support nested components, we only need to check one level deep
|
||
|
|
else if (!Parent->HasAnyFlags(RF_ClassDefaultObject) && Parent->GetOuter()->HasAnyFlags(RF_ClassDefaultObject))
|
||
|
|
{
|
||
|
|
Object = Parent->GetOuter();
|
||
|
|
Cls = Parent->GetOuter()->GetClass();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Object = Parent;
|
||
|
|
Cls = Parent->GetClass();
|
||
|
|
}
|
||
|
|
FCString::Strncpy(FuncName, ObjName, i + 1);
|
||
|
|
}
|
||
|
|
if (Cls == nullptr)
|
||
|
|
{
|
||
|
|
Cls = FindObject<UClass>(ANY_PACKAGE, ObjName);
|
||
|
|
if (Cls != nullptr)
|
||
|
|
{
|
||
|
|
Object = Cls->GetDefaultObject();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Check outer chain before checking all packages
|
||
|
|
UObject* OuterToCheck = Parent;
|
||
|
|
while (Object == nullptr && OuterToCheck)
|
||
|
|
{
|
||
|
|
Object = StaticFindObject(UObject::StaticClass(), OuterToCheck, ObjName);
|
||
|
|
OuterToCheck = OuterToCheck->GetOuter();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Object == nullptr)
|
||
|
|
{
|
||
|
|
Object = StaticFindObject(UObject::StaticClass(), ANY_PACKAGE, ObjName);
|
||
|
|
}
|
||
|
|
if (Object != nullptr)
|
||
|
|
{
|
||
|
|
Cls = Object->GetClass();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
UFunction* Func = FindUField<UFunction>(Cls, FuncName);
|
||
|
|
// Check function params.
|
||
|
|
if (Func != nullptr)
|
||
|
|
{
|
||
|
|
// Find the delegate UFunction to check params
|
||
|
|
check(SignatureFunction != nullptr && "Invalid delegate property");
|
||
|
|
|
||
|
|
// check return type and params
|
||
|
|
if (Func->NumParms == SignatureFunction->NumParms)
|
||
|
|
{
|
||
|
|
int32 Count = 0;
|
||
|
|
for (TFieldIterator<FProperty> It1(Func), It2(SignatureFunction); Count < SignatureFunction->NumParms; ++It1, ++It2, ++Count)
|
||
|
|
{
|
||
|
|
if (It1->GetClass() != It2->GetClass() || (It1->PropertyFlags & CPF_OutParm) != (It2->PropertyFlags & CPF_OutParm))
|
||
|
|
{
|
||
|
|
ErrorText->Logf(ELogVerbosity::Warning, TEXT("Function %s does not match param types with delegate signature %s"), *Func->GetName(), *SignatureFunction->GetName());
|
||
|
|
Func = nullptr;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
ErrorText->Logf(ELogVerbosity::Warning, TEXT("Function %s does not match number of params with delegate signature %s"), *Func->GetName(), *SignatureFunction->GetName());
|
||
|
|
Func = nullptr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
ErrorText->Logf(ELogVerbosity::Warning, TEXT("Unable to find function %s in object %s for delegate (found class: %s)"), FuncName, ObjName, *GetNameSafe(Cls));
|
||
|
|
}
|
||
|
|
|
||
|
|
// UE_LOG(LogProperty, Log, TEXT("... importing delegate FunctionName:'%s'(%s) Object:'%s'(%s)"),Func != nullptr ? *Func->GetName() : TEXT("nullptr"), FuncName, Object != nullptr ? *Object->GetFullName() : TEXT("nullptr"), ObjName);
|
||
|
|
|
||
|
|
Delegate.BindUFunction(Func ? Object : nullptr, Func ? Func->GetFName() : NAME_None);
|
||
|
|
|
||
|
|
return (Func != nullptr && Object != nullptr) ? Buffer : nullptr;
|
||
|
|
}
|
||
|
|
} // namespace DelegatePropertyTools
|