3719 lines
127 KiB
C++
3719 lines
127 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
ScriptCore.cpp: Kismet VM execution and support code.
|
|
=============================================================================*/
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Logging/LogScopedCategoryAndVerbosityOverride.h"
|
|
#include "Stats/Stats.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/App.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/ScriptInterface.h"
|
|
#include "UObject/Script.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/UObjectBaseUtility.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/CoreNative.h"
|
|
#include "UObject/Class.h"
|
|
#include "Templates/Casts.h"
|
|
#include "UObject/SoftObjectPtr.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/Stack.h"
|
|
#include "Blueprint/BlueprintSupport.h"
|
|
#include "UObject/ScriptMacros.h"
|
|
#include "Misc/HotReloadInterface.h"
|
|
#include "UObject/UObjectThreadContext.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogScriptFrame);
|
|
DEFINE_LOG_CATEGORY_STATIC(LogScriptCore, Log, All);
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Blueprint Time"), STAT_BlueprintTime, STATGROUP_Game);
|
|
|
|
#define LOCTEXT_NAMESPACE "ScriptCore"
|
|
|
|
#if TOTAL_OVERHEAD_SCRIPT_STATS
|
|
DEFINE_STAT(STAT_ScriptVmTime_Total);
|
|
DEFINE_STAT(STAT_ScriptNativeTime_Total);
|
|
#endif // TOTAL_OVERHEAD_SCRIPT_STATS
|
|
|
|
static int32 GVerboseScriptStats = 0;
|
|
static FAutoConsoleVariableRef CVarVerboseScriptStats(
|
|
TEXT("bp.VerboseStats"),
|
|
GVerboseScriptStats,
|
|
TEXT("Create additional stats for Blueprint execution.\n"),
|
|
ECVF_Default);
|
|
|
|
static int32 GShortScriptWarnings = 0;
|
|
static FAutoConsoleVariableRef CVarShortScriptWarnings(
|
|
TEXT("bp.ShortScriptWarnings"),
|
|
GShortScriptWarnings,
|
|
TEXT("Shorten the blueprint exception logs.\n"),
|
|
ECVF_Default);
|
|
|
|
static int32 GScriptRecurseLimit = 120;
|
|
static FAutoConsoleVariableRef CVarScriptRecurseLimit(
|
|
TEXT("bp.ScriptRecurseLimit"),
|
|
GScriptRecurseLimit,
|
|
TEXT("Sets the number of recursions before script is considered in an infinite loop.\n"),
|
|
ECVF_Default);
|
|
|
|
#if PER_FUNCTION_SCRIPT_STATS
|
|
static int32 GMaxFunctionStatDepth = -1;
|
|
static FAutoConsoleVariableRef CVarMaxFunctionStatDepth(
|
|
TEXT("bp.MaxFunctionStatDepth"),
|
|
GMaxFunctionStatDepth,
|
|
TEXT("Script stack threshold for recording per function stats.\n")
|
|
TEXT("-1: Record all function stats (default)\n")
|
|
TEXT("0: Record no function stats\n")
|
|
TEXT(">0: Record functions with depth < MaxFunctionStatDepth \n"),
|
|
ECVF_Default);
|
|
#endif
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Globals.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
//
|
|
// Native function table.
|
|
//
|
|
COREUOBJECT_API FNativeFuncPtr GNatives[EX_Max];
|
|
COREUOBJECT_API int32 GNativeDuplicate = 0;
|
|
|
|
COREUOBJECT_API FNativeFuncPtr GCasts[CST_Max];
|
|
COREUOBJECT_API int32 GCastDuplicate = 0;
|
|
|
|
COREUOBJECT_API int32 GMaximumScriptLoopIterations = 1000000;
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
|
|
#define CHECK_RUNAWAY { FBlueprintContextTracker::Get().AddRunaway(); }
|
|
COREUOBJECT_API void GInitRunaway()
|
|
{
|
|
FBlueprintContextTracker::Get().ResetRunaway();
|
|
}
|
|
|
|
#else
|
|
|
|
#define CHECK_RUNAWAY
|
|
COREUOBJECT_API void GInitRunaway() {}
|
|
|
|
#endif // DO_BLUEPRINT_GUARD
|
|
|
|
#define STORE_INSTRUCTION_NAMES SCRIPT_AUDIT_ROUTINES
|
|
|
|
#if STORE_INSTRUCTION_NAMES
|
|
const char* GNativeFuncNames[EX_Max];
|
|
|
|
#define STORE_INSTRUCTION_NAME(inst) \
|
|
static struct F##inst##Registrar \
|
|
{ \
|
|
F##inst##Registrar() \
|
|
{ \
|
|
GNativeFuncNames[inst] = #inst; \
|
|
} \
|
|
} inst##RegistrarInst;
|
|
#else
|
|
#define STORE_INSTRUCTION_NAME(inst)
|
|
#endif // STORE_INSTRUCTION_NAMES
|
|
|
|
#define IMPLEMENT_FUNCTION(func) \
|
|
static FNativeFunctionRegistrar UObject##func##Registar(UObject::StaticClass(),#func,&UObject::func);
|
|
|
|
#define IMPLEMENT_CAST_FUNCTION(CastIndex, func) \
|
|
IMPLEMENT_FUNCTION(func); \
|
|
static uint8 UObject##func##CastTemp = GRegisterCast( CastIndex, &UObject::func );
|
|
|
|
#define IMPLEMENT_VM_FUNCTION(BytecodeIndex, func) \
|
|
STORE_INSTRUCTION_NAME(BytecodeIndex) \
|
|
IMPLEMENT_FUNCTION(func) \
|
|
static uint8 UObject##func##BytecodeTemp = GRegisterNative( BytecodeIndex, &UObject::func );
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FBlueprintCoreDelegates
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
FBlueprintCoreDelegates::FOnScriptDebuggingEvent FBlueprintCoreDelegates::OnScriptException;
|
|
FBlueprintCoreDelegates::FOnScriptExecutionEnd FBlueprintCoreDelegates::OnScriptExecutionEnd;
|
|
FBlueprintCoreDelegates::FOnScriptInstrumentEvent FBlueprintCoreDelegates::OnScriptProfilingEvent;
|
|
FBlueprintCoreDelegates::FOnToggleScriptProfiler FBlueprintCoreDelegates::OnToggleScriptProfiler;
|
|
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
void FBlueprintCoreDelegates::ThrowScriptException(const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info)
|
|
{
|
|
bool bShouldLogWarning = true;
|
|
|
|
switch (Info.GetType())
|
|
{
|
|
case EBlueprintExceptionType::Breakpoint:
|
|
case EBlueprintExceptionType::Tracepoint:
|
|
case EBlueprintExceptionType::WireTracepoint:
|
|
// These shouldn't warn (they're just to pass the exception into the editor via the delegate below)
|
|
bShouldLogWarning = false;
|
|
break;
|
|
#if WITH_EDITOR && DO_BLUEPRINT_GUARD
|
|
case EBlueprintExceptionType::AccessViolation:
|
|
bShouldLogWarning = FBlueprintContextTracker::Get().RecordAccessViolation(ActiveObject);
|
|
break;
|
|
#endif // WITH_EDITOR && DO_BLUEPRINT_GUARD
|
|
default:
|
|
// Other unhandled cases should always emit a warning
|
|
break;
|
|
}
|
|
|
|
if (bShouldLogWarning)
|
|
{
|
|
UE_SUPPRESS(LogScript, Warning, const_cast<FFrame*>(&StackFrame)->Logf(TEXT("%s"), *(Info.GetDescription().ToString())));
|
|
}
|
|
|
|
// cant fire arbitrary delegates here off the game thread
|
|
if (IsInGameThread())
|
|
{
|
|
#if DO_BLUEPRINT_GUARD
|
|
// If nothing is bound, show warnings so something is left in the log.
|
|
if (bShouldLogWarning && (OnScriptException.IsBound() == false) && !GShortScriptWarnings)
|
|
{
|
|
UE_LOG(LogScript, Warning, TEXT("%s"), *StackFrame.GetStackTrace());
|
|
}
|
|
#endif
|
|
OnScriptException.Broadcast(ActiveObject, StackFrame, Info);
|
|
}
|
|
|
|
if (Info.GetType() == EBlueprintExceptionType::FatalError)
|
|
{
|
|
// Crash maybe?
|
|
}
|
|
}
|
|
|
|
void FBlueprintCoreDelegates::InstrumentScriptEvent(const FScriptInstrumentationSignal& Info)
|
|
{
|
|
OnScriptProfilingEvent.Broadcast(Info);
|
|
}
|
|
|
|
void FBlueprintCoreDelegates::SetScriptMaximumLoopIterations(const int32 MaximumLoopIterations)
|
|
{
|
|
if (ensure(MaximumLoopIterations > 0))
|
|
{
|
|
GMaximumScriptLoopIterations = MaximumLoopIterations;
|
|
}
|
|
}
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
|
|
FBlueprintContextTracker::FOnEnterScriptContext FBlueprintContextTracker::OnEnterScriptContext;
|
|
FBlueprintContextTracker::FOnExitScriptContext FBlueprintContextTracker::OnExitScriptContext;
|
|
|
|
FBlueprintContextTracker& FBlueprintContextTracker::Get()
|
|
{
|
|
return TThreadSingleton<FBlueprintContextTracker>::Get();
|
|
}
|
|
|
|
const FBlueprintContextTracker* FBlueprintContextTracker::TryGet()
|
|
{
|
|
return TThreadSingleton<FBlueprintContextTracker>::TryGet();
|
|
}
|
|
|
|
void FBlueprintContextTracker::ResetRunaway()
|
|
{
|
|
Runaway = 0;
|
|
Recurse = 0;
|
|
bRanaway = false;
|
|
}
|
|
|
|
void FBlueprintContextTracker::EnterScriptContext(const UObject* ContextObject, const UFunction* ContextFunction)
|
|
{
|
|
ScriptEntryTag++;
|
|
|
|
if (IsInGameThread())
|
|
{
|
|
// Multicast delegate broadcast is not safe, this will be refactored later to completely disable in other threads
|
|
OnEnterScriptContext.Broadcast(*this, ContextObject, ContextFunction);
|
|
}
|
|
}
|
|
|
|
void FBlueprintContextTracker::ExitScriptContext()
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
OnExitScriptContext.Broadcast(*this);
|
|
}
|
|
|
|
ScriptEntryTag--;
|
|
|
|
check(ScriptEntryTag >= 0);
|
|
}
|
|
|
|
bool FBlueprintContextTracker::RecordAccessViolation(const UObject* Object)
|
|
{
|
|
// Determine if the access none should warn or not (we suppress warnings beyond a certain count for each object to avoid per-frame spaminess)
|
|
struct FIntConfigValueHelper
|
|
{
|
|
int32 Value;
|
|
|
|
FIntConfigValueHelper(): Value(0)
|
|
{
|
|
GConfig->GetInt(TEXT("ScriptErrorLog"), TEXT("MaxNumOfAccessViolation"), Value, GEditorIni);
|
|
}
|
|
};
|
|
|
|
static const FIntConfigValueHelper MaxNumOfAccessViolation;
|
|
if (MaxNumOfAccessViolation.Value > 0)
|
|
{
|
|
const FName ActiveObjectName = Object ? Object->GetFName() : FName();
|
|
int32& Num = DisplayedWarningsMap.FindOrAdd(ActiveObjectName);
|
|
Num++;
|
|
if (Num > MaxNumOfAccessViolation.Value)
|
|
{
|
|
// Skip the generic warning, we've hit this one too many times
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This is meant to be called from the immediate mode, and for confusing reasons the optimized code isn't always safe in that case
|
|
PRAGMA_DISABLE_OPTIMIZATION
|
|
|
|
void PrintScriptCallStackImpl()
|
|
{
|
|
const FBlueprintContextTracker* BlueprintExceptionTracker = FBlueprintContextTracker::TryGet();
|
|
if (BlueprintExceptionTracker)
|
|
{
|
|
const TArray<const FFrame*>& RawStack = BlueprintExceptionTracker->GetScriptStack();
|
|
FString ScriptStack = FString::Printf(TEXT("\n\nScript Stack (%d frames):\n"), RawStack.Num());
|
|
for (int32 FrameIdx = RawStack.Num() - 1; FrameIdx >= 0; --FrameIdx)
|
|
{
|
|
ScriptStack += RawStack[FrameIdx]->GetStackDescription() + TEXT("\n");
|
|
}
|
|
UE_LOG(LogOutputDevice, Warning, TEXT("%s"), *ScriptStack);
|
|
}
|
|
}
|
|
|
|
PRAGMA_ENABLE_OPTIMIZATION
|
|
|
|
extern CORE_API void (*GPrintScriptCallStackFn)();
|
|
|
|
#endif // DO_BLUEPRINT_GUARD
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FEditorScriptExecutionGuard
|
|
FEditorScriptExecutionGuard::FEditorScriptExecutionGuard()
|
|
: bOldGAllowScriptExecutionInEditor(GAllowActorScriptExecutionInEditor)
|
|
{
|
|
GAllowActorScriptExecutionInEditor = true;
|
|
|
|
if (GIsEditor && !FApp::IsGame())
|
|
{
|
|
GInitRunaway();
|
|
}
|
|
}
|
|
|
|
FEditorScriptExecutionGuard::~FEditorScriptExecutionGuard()
|
|
{
|
|
GAllowActorScriptExecutionInEditor = bOldGAllowScriptExecutionInEditor;
|
|
}
|
|
|
|
bool IsValidCPPIdentifierChar(TCHAR Char)
|
|
{
|
|
return Char == TCHAR('_') || (Char >= TCHAR('a') && Char <= TCHAR('z')) || (Char >= TCHAR('A') && Char <= TCHAR('Z')) || (Char >= TCHAR('0') && Char <= TCHAR('9'));
|
|
}
|
|
|
|
FString ToValidCPPIdentifierChars(TCHAR Char)
|
|
{
|
|
FString Ret;
|
|
int32 RawValue = Char;
|
|
int32 Counter = 0;
|
|
while (RawValue != 0)
|
|
{
|
|
int32 Digit = RawValue % 63;
|
|
RawValue = (RawValue - Digit) / 63;
|
|
|
|
TCHAR SafeChar;
|
|
if (Digit <= 25)
|
|
{
|
|
SafeChar = TCHAR(TCHAR('a') + (25 - Digit));
|
|
}
|
|
else if (Digit <= 51)
|
|
{
|
|
SafeChar = TCHAR(TCHAR('A') + (51 - Digit));
|
|
}
|
|
else if (Digit <= 61)
|
|
{
|
|
SafeChar = TCHAR(TCHAR('0') + (61 - Digit));
|
|
}
|
|
else
|
|
{
|
|
check(Digit == 62);
|
|
SafeChar = TCHAR('_');
|
|
}
|
|
|
|
Ret.AppendChar(SafeChar);
|
|
}
|
|
return Ret;
|
|
}
|
|
|
|
FString UnicodeToCPPIdentifier(const FString& InName, bool bDeprecated, const TCHAR* Prefix)
|
|
{
|
|
// FName's can contain unicode characters or collide with other CPP identifiers or keywords. This function
|
|
// returns a string that will have a prefix which is unlikely to collide with existing identifiers and
|
|
// converts unicode characters in place to valid ascii characters. Strictly speaking a C++ compiler *could*
|
|
// support unicode identifiers in source files, but I am not comfortable relying on this behavior.
|
|
|
|
FString Ret = InName;
|
|
// Initialize postfix with a unique identifier. This prevents potential collisions between names that have unicode
|
|
// characters and those that do not. The drawback is that it is not safe to put '__pf' in a blueprint name.
|
|
FString Postfix = TEXT("__pf");
|
|
for (auto& Char: Ret)
|
|
{
|
|
// if the character is not a valid character for a c++ identifier, then we need to encode it using valid characters:
|
|
if (!IsValidCPPIdentifierChar(Char))
|
|
{
|
|
// deterministically map char to a valid ascii character, we have 63 characters available (aA-zZ, 0-9, and _)
|
|
// so the optimal encoding would be base 63:
|
|
Postfix.Append(ToValidCPPIdentifierChars(Char));
|
|
Char = TCHAR('x');
|
|
}
|
|
}
|
|
|
|
FString PrefixStr(Prefix);
|
|
// fix for error C2059: syntax error : 'bad suffix on number'
|
|
if (!PrefixStr.Len() && Ret.Len() && FChar::IsDigit(Ret[0]))
|
|
{
|
|
Ret.InsertAt(0, TCHAR('_'));
|
|
}
|
|
Ret = PrefixStr + Ret + Postfix;
|
|
|
|
// Workaround for a strange compiler error
|
|
if (InName == TEXT("Replicate to server"))
|
|
{
|
|
Ret = TEXT("MagicNameWorkaround");
|
|
}
|
|
|
|
return bDeprecated ? Ret + TEXT("_DEPRECATED") : Ret;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
FFrame implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
void FFrame::Step(UObject* Context, RESULT_DECL)
|
|
{
|
|
int32 B = *Code++;
|
|
(GNatives[B])(Context, *this, RESULT_PARAM);
|
|
}
|
|
|
|
void FFrame::StepExplicitProperty(void* const Result, FProperty* Property)
|
|
{
|
|
checkSlow(Result != NULL);
|
|
|
|
if (Property->PropertyFlags & CPF_OutParm)
|
|
{
|
|
// look through the out parameter infos and find the one that has the address of this property
|
|
FOutParmRec* Out = OutParms;
|
|
checkSlow(Out);
|
|
while (Out->Property != Property)
|
|
{
|
|
Out = Out->NextOutParm;
|
|
checkSlow(Out);
|
|
}
|
|
MostRecentPropertyAddress = Out->PropAddr;
|
|
// no need to copy property value, since the caller is just looking for MostRecentPropertyAddress
|
|
}
|
|
else
|
|
{
|
|
MostRecentPropertyAddress = Property->ContainerPtrToValuePtr<uint8>(Locals);
|
|
Property->CopyCompleteValueToScriptVM(Result, MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Helper function that checks commandline and Engine ini to see whether
|
|
// script stack should be shown on warnings
|
|
static bool ShowKismetScriptStackOnWarnings()
|
|
{
|
|
static bool ShowScriptStackForScriptWarning = false;
|
|
static bool CheckScriptWarningOptions = false;
|
|
|
|
if (!CheckScriptWarningOptions)
|
|
{
|
|
GConfig->GetBool(TEXT("Kismet"), TEXT("ScriptStackOnWarnings"), ShowScriptStackForScriptWarning, GEngineIni);
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("SCRIPTSTACKONWARNINGS")))
|
|
{
|
|
ShowScriptStackForScriptWarning = true;
|
|
}
|
|
|
|
CheckScriptWarningOptions = true;
|
|
}
|
|
|
|
return ShowScriptStackForScriptWarning;
|
|
}
|
|
|
|
FString FFrame::GetScriptCallstack(bool bReturnEmpty)
|
|
{
|
|
FString ScriptStack;
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
FBlueprintContextTracker& BlueprintExceptionTracker = FBlueprintContextTracker::Get();
|
|
if (BlueprintExceptionTracker.ScriptStack.Num() > 0)
|
|
{
|
|
for (int32 i = BlueprintExceptionTracker.ScriptStack.Num() - 1; i >= 0; --i)
|
|
{
|
|
ScriptStack += TEXT("\t") + BlueprintExceptionTracker.ScriptStack[i]->GetStackDescription() + TEXT("\n");
|
|
}
|
|
}
|
|
else if (!bReturnEmpty)
|
|
{
|
|
ScriptStack += TEXT("\t[Empty] (FFrame::GetScriptCallstack() called from native code)");
|
|
}
|
|
#else
|
|
if (!bReturnEmpty)
|
|
{
|
|
ScriptStack = TEXT("Unable to display Script Callstack. Compile with DO_BLUEPRINT_GUARD=1");
|
|
}
|
|
#endif
|
|
|
|
return ScriptStack;
|
|
}
|
|
|
|
FString FFrame::GetStackDescription() const
|
|
{
|
|
return Node->GetOuter()->GetName() + TEXT(".") + Node->GetName();
|
|
}
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
void FFrame::InitPrintScriptCallstack()
|
|
{
|
|
GPrintScriptCallStackFn = &PrintScriptCallStackImpl;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Error or warning handler.
|
|
//
|
|
//@TODO: This function should take more information in, or be able to gather it from the callstack!
|
|
void FFrame::KismetExecutionMessage(const TCHAR* Message, ELogVerbosity::Type Verbosity, FName WarningId)
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
// Optionally always treat errors/warnings as bad
|
|
if (Verbosity <= ELogVerbosity::Warning && FParse::Param(FCommandLine::Get(), TEXT("FATALSCRIPTWARNINGS")))
|
|
{
|
|
Verbosity = ELogVerbosity::Fatal;
|
|
}
|
|
else if (Verbosity == ELogVerbosity::Warning && WarningId != FName())
|
|
{
|
|
// check to see if this specific warning has been elevated to an error:
|
|
if (FBlueprintSupport::ShouldTreatWarningAsError(WarningId))
|
|
{
|
|
Verbosity = ELogVerbosity::Error;
|
|
}
|
|
else if (FBlueprintSupport::ShouldSuppressWarning(WarningId))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FString ScriptStack;
|
|
|
|
// Tracking down some places that display warnings but no message..
|
|
ensureAlways(Verbosity > ELogVerbosity::Warning || FCString::Strlen(Message) > 0);
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
// Show the stack for fatal/error, and on warning if that option is enabled
|
|
if (Verbosity <= ELogVerbosity::Error || (ShowKismetScriptStackOnWarnings() && Verbosity == ELogVerbosity::Warning))
|
|
{
|
|
ScriptStack = TEXT("Script call stack:\n");
|
|
ScriptStack += GetScriptCallstack();
|
|
}
|
|
#endif
|
|
|
|
if (Verbosity == ELogVerbosity::Fatal)
|
|
{
|
|
UE_LOG(LogScriptCore, Fatal, TEXT("Script Msg: %s\n%s"), Message, *ScriptStack);
|
|
}
|
|
#if NO_LOGGING
|
|
else
|
|
#else
|
|
else if (!LogScriptCore.IsSuppressed(Verbosity))
|
|
#endif
|
|
{
|
|
FScriptExceptionHandler::Get().HandleException(Verbosity, Message, *ScriptStack);
|
|
}
|
|
}
|
|
|
|
void FFrame::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category)
|
|
{
|
|
// Treat errors/warnings as bad
|
|
if (Verbosity == ELogVerbosity::Warning)
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
static bool GTreatScriptWarningsFatal = FParse::Param(FCommandLine::Get(), TEXT("FATALSCRIPTWARNINGS"));
|
|
if (GTreatScriptWarningsFatal)
|
|
{
|
|
Verbosity = ELogVerbosity::Error;
|
|
}
|
|
#endif
|
|
}
|
|
if (Verbosity == ELogVerbosity::Error)
|
|
{
|
|
UE_LOG(LogScriptCore, Fatal,
|
|
TEXT("%s\r\n\t%s\r\n\t%s:%04X\r\n\t%s"),
|
|
V,
|
|
*Object->GetFullName(),
|
|
*Node->GetFullName(),
|
|
Code - Node->Script.GetData(),
|
|
*GetStackTrace());
|
|
}
|
|
else
|
|
{
|
|
#if DO_BLUEPRINT_GUARD
|
|
if (GShortScriptWarnings)
|
|
{
|
|
UE_LOG(LogScript, Warning,
|
|
TEXT("%s Object(%s) %s:%04X"),
|
|
V,
|
|
*Object->GetName(),
|
|
*Node->GetName(),
|
|
Code - Node->Script.GetData());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogScript, Warning,
|
|
TEXT("%s\r\n\t%s\r\n\t%s:%04X%s"),
|
|
V,
|
|
*Object->GetFullName(),
|
|
*Node->GetFullName(),
|
|
Code - Node->Script.GetData(),
|
|
ShowKismetScriptStackOnWarnings() ? *(FString(TEXT("\r\n")) + GetStackTrace()) : TEXT(""));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
FString FFrame::GetStackTrace() const
|
|
{
|
|
FString Result;
|
|
|
|
// travel down the stack recording the frames
|
|
TArray<const FFrame*> FrameStack;
|
|
const FFrame* CurrFrame = this;
|
|
while (CurrFrame != NULL)
|
|
{
|
|
FrameStack.Add(CurrFrame);
|
|
CurrFrame = CurrFrame->PreviousFrame;
|
|
}
|
|
|
|
// and then dump them to a string
|
|
if (FrameStack.Num() > 0)
|
|
{
|
|
Result += FString(TEXT("Script call stack:\n"));
|
|
for (int32 Index = FrameStack.Num() - 1; Index >= 0; Index--)
|
|
{
|
|
Result += FString::Printf(TEXT("\t%s\n"), *FrameStack[Index]->Node->GetFullName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Result += FString(TEXT("Script call stack: [Empty] (FFrame::GetStackTrace() called from native code)"));
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FScriptInstrumentationSignal
|
|
|
|
FScriptInstrumentationSignal::FScriptInstrumentationSignal(EScriptInstrumentation::Type InEventType, const UObject* InContextObject, const struct FFrame& InStackFrame, const FName EventNameIn)
|
|
: EventType(InEventType), ContextObject(InContextObject), Function(InStackFrame.Node), EventName(EventNameIn), StackFramePtr(&InStackFrame), LatentLinkId(INDEX_NONE)
|
|
{
|
|
}
|
|
|
|
const UClass* FScriptInstrumentationSignal::GetClass() const
|
|
{
|
|
return ContextObject ? ContextObject->GetClass() : nullptr;
|
|
}
|
|
|
|
const UClass* FScriptInstrumentationSignal::GetFunctionClassScope() const
|
|
{
|
|
return Function->GetOuterUClass();
|
|
}
|
|
|
|
FName FScriptInstrumentationSignal::GetFunctionName() const
|
|
{
|
|
return EventName.IsNone() ? Function->GetFName() : EventName;
|
|
}
|
|
|
|
int32 FScriptInstrumentationSignal::GetScriptCodeOffset() const
|
|
{
|
|
int32 CodeOffset = INDEX_NONE;
|
|
if (EventType == EScriptInstrumentation::ResumeEvent)
|
|
{
|
|
// Resume events require the link id rather than script code offset
|
|
CodeOffset = LatentLinkId;
|
|
}
|
|
else if (StackFramePtr != nullptr)
|
|
{
|
|
CodeOffset = StackFramePtr->Code - StackFramePtr->Node->Script.GetData() - 1;
|
|
}
|
|
return CodeOffset;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Global script execution functions.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Native registry.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
//
|
|
// Register a native function.
|
|
// Warning: Called at startup time, before engine initialization.
|
|
//
|
|
COREUOBJECT_API uint8 GRegisterNative(int32 NativeBytecodeIndex, const FNativeFuncPtr& Func)
|
|
{
|
|
static bool bInitialized = false;
|
|
if (!bInitialized)
|
|
{
|
|
bInitialized = true;
|
|
for (uint32 i = 0; i < UE_ARRAY_COUNT(GNatives); i++)
|
|
{
|
|
GNatives[i] = &UObject::execUndefined;
|
|
}
|
|
}
|
|
|
|
if (NativeBytecodeIndex != INDEX_NONE)
|
|
{
|
|
if (NativeBytecodeIndex < 0 || (uint32)NativeBytecodeIndex > UE_ARRAY_COUNT(GNatives) || GNatives[NativeBytecodeIndex] != &UObject::execUndefined)
|
|
{
|
|
#if WITH_HOT_RELOAD
|
|
if (GIsHotReload)
|
|
{
|
|
IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
|
|
CA_SUPPRESS(6385)
|
|
HotReloadSupport.AddHotReloadFunctionRemap(Func, GNatives[NativeBytecodeIndex]);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
GNativeDuplicate = NativeBytecodeIndex;
|
|
}
|
|
}
|
|
CA_SUPPRESS(6386)
|
|
GNatives[NativeBytecodeIndex] = Func;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
COREUOBJECT_API uint8 GRegisterCast(int32 CastCode, const FNativeFuncPtr& Func)
|
|
{
|
|
static int32 bInitialized = false;
|
|
if (!bInitialized)
|
|
{
|
|
bInitialized = true;
|
|
for (uint32 i = 0; i < UE_ARRAY_COUNT(GCasts); i++)
|
|
{
|
|
GCasts[i] = &UObject::execUndefined;
|
|
}
|
|
}
|
|
|
|
//@TODO: UCREMOVAL: Remove rest of cast machinery
|
|
check((CastCode == CST_ObjectToBool) || (CastCode == CST_ObjectToInterface) || (CastCode == CST_InterfaceToBool));
|
|
|
|
if (CastCode != INDEX_NONE)
|
|
{
|
|
if (
|
|
#if WITH_HOT_RELOAD
|
|
!GIsHotReload &&
|
|
#endif
|
|
(CastCode < 0 || (uint32)CastCode > UE_ARRAY_COUNT(GCasts) || GCasts[CastCode] != &UObject::execUndefined))
|
|
{
|
|
GCastDuplicate = CastCode;
|
|
}
|
|
GCasts[CastCode] = Func;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void UObject::SkipFunction(FFrame& Stack, RESULT_DECL, UFunction* Function)
|
|
{
|
|
// allocate temporary memory on the stack for evaluating parameters
|
|
uint8* Frame = (uint8*)FMemory_Alloca(Function->PropertiesSize);
|
|
FMemory::Memzero(Frame, Function->PropertiesSize);
|
|
for (FProperty* Property = (FProperty*)(Function->ChildProperties); *Stack.Code != EX_EndFunctionParms; Property = (FProperty*)(Property->Next))
|
|
{
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
// evaluate the expression into our temporary memory space
|
|
// it'd be nice to be able to skip the copy, but most native functions assume a non-NULL Result pointer
|
|
// so we can only do that if we know the expression is an l-value (out parameter)
|
|
Stack.Step(Stack.Object, (Property->PropertyFlags & CPF_OutParm) ? NULL : Property->ContainerPtrToValuePtr<uint8>(Frame));
|
|
}
|
|
|
|
// advance the code past EX_EndFunctionParms
|
|
Stack.Code++;
|
|
|
|
// destruct properties requiring it for which we had to use our temporary memory
|
|
// @warning: conditions for skipping DestroyValue() here must match conditions for passing NULL to Stack.Step() above
|
|
for (FProperty* Destruct = Function->DestructorLink; Destruct; Destruct = Destruct->DestructorLinkNext)
|
|
{
|
|
if (!Destruct->HasAnyPropertyFlags(CPF_OutParm))
|
|
{
|
|
Destruct->DestroyValue_InContainer(Frame);
|
|
}
|
|
}
|
|
|
|
FProperty* ReturnProp = Function->GetReturnProperty();
|
|
if (ReturnProp != NULL)
|
|
{
|
|
// destroy old value if necessary
|
|
ReturnProp->DestroyValue(RESULT_PARAM);
|
|
// copy zero value for return property into Result
|
|
FMemory::Memzero(RESULT_PARAM, ReturnProp->ArrayDim * ReturnProp->ElementSize);
|
|
}
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4750) // warning C4750: function with _alloca() inlined into a loop
|
|
#endif
|
|
|
|
// Helper function to set up a script function, and then execute it using ExecFtor. This is
|
|
// a template function because we use alloca to allocate space for parameters and results,
|
|
// but we also have two hotpaths: normal function calls which must call GetFunctionCallspace,
|
|
// and normal bytecode functions that are local only!
|
|
template <typename Exec>
|
|
void ProcessScriptFunction(UObject* Context, UFunction* Function, FFrame& Stack, RESULT_DECL, Exec ExecFtor)
|
|
{
|
|
check(!Function->HasAnyFunctionFlags(FUNC_Native));
|
|
|
|
// Allocate any temporary memory the script may need via AllocA. This AllocA dependency, along with
|
|
// the desire to inline calls to our Execution function are the reason for this template function:
|
|
uint8* FrameMemory = nullptr;
|
|
FFrame NewStack(Context, Function, nullptr, &Stack, Function->ChildProperties);
|
|
#if USE_UBER_GRAPH_PERSISTENT_FRAME
|
|
FrameMemory = Function->GetOuterUClassUnchecked()->GetPersistentUberGraphFrame(Context, Function);
|
|
#endif
|
|
bool bUsePersistentFrame = (nullptr != FrameMemory);
|
|
if (!bUsePersistentFrame)
|
|
{
|
|
FrameMemory = (uint8*)FMemory_Alloca(Function->PropertiesSize);
|
|
FMemory::Memzero(FrameMemory, Function->PropertiesSize);
|
|
}
|
|
|
|
/*
|
|
Allocate space for return value bookkeeping - rarely used by bytecode functions,
|
|
but necessary in cases where a bytecode function's signature needs to match
|
|
a native function:
|
|
*/
|
|
if (Function->ReturnValueOffset != MAX_uint16)
|
|
{
|
|
FProperty* ReturnProperty = Function->GetReturnProperty();
|
|
if (ensure(ReturnProperty))
|
|
{
|
|
FOutParmRec* RetVal = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec));
|
|
|
|
/* Our context should be that we're in a variable assignment to the return value, so ensure that we have a valid property to return to */
|
|
check(RESULT_PARAM != NULL);
|
|
RetVal->PropAddr = (uint8*)RESULT_PARAM;
|
|
RetVal->Property = ReturnProperty;
|
|
NewStack.OutParms = RetVal;
|
|
}
|
|
}
|
|
|
|
NewStack.Locals = FrameMemory;
|
|
FOutParmRec** LastOut = &NewStack.OutParms;
|
|
|
|
for (FProperty* Property = (FProperty*)(Function->ChildProperties); *Stack.Code != EX_EndFunctionParms; Property = (FProperty*)(Property->Next))
|
|
{
|
|
checkfSlow(Property, TEXT("NULL Property in Function %s"), *Function->GetPathName());
|
|
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
|
|
// Skip the return parameter case, as we've already handled it above
|
|
const bool bIsReturnParam = ((Property->PropertyFlags & CPF_ReturnParm) != 0);
|
|
if (bIsReturnParam)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Property->PropertyFlags & CPF_OutParm)
|
|
{
|
|
// evaluate the expression for this parameter, which sets Stack.MostRecentPropertyAddress to the address of the property accessed
|
|
Stack.Step(Stack.Object, NULL);
|
|
|
|
CA_SUPPRESS(6263)
|
|
FOutParmRec* Out = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec));
|
|
// set the address and property in the out param info
|
|
// warning: Stack.MostRecentPropertyAddress could be NULL for optional out parameters
|
|
// if that's the case, we use the extra memory allocated for the out param in the function's locals
|
|
// so there's always a valid address
|
|
ensure(Stack.MostRecentPropertyAddress); // possible problem - output param values on local stack are neither initialized nor cleaned.
|
|
Out->PropAddr = (Stack.MostRecentPropertyAddress != NULL) ? Stack.MostRecentPropertyAddress : Property->ContainerPtrToValuePtr<uint8>(NewStack.Locals);
|
|
Out->Property = Property;
|
|
|
|
// add the new out param info to the stack frame's linked list
|
|
if (*LastOut)
|
|
{
|
|
(*LastOut)->NextOutParm = Out;
|
|
LastOut = &(*LastOut)->NextOutParm;
|
|
}
|
|
else
|
|
{
|
|
*LastOut = Out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// copy the result of the expression for this parameter into the appropriate part of the local variable space
|
|
uint8* Param = Property->ContainerPtrToValuePtr<uint8>(NewStack.Locals);
|
|
checkSlow(Param);
|
|
|
|
Property->InitializeValue_InContainer(NewStack.Locals);
|
|
|
|
Stack.Step(Stack.Object, Param);
|
|
}
|
|
}
|
|
Stack.Code++;
|
|
// set the next pointer of the last item to NULL to mark the end of the list
|
|
if (*LastOut)
|
|
{
|
|
(*LastOut)->NextOutParm = NULL;
|
|
}
|
|
|
|
if (!bUsePersistentFrame)
|
|
{
|
|
// Initialize any local struct properties with defaults
|
|
for (FProperty* LocalProp = Function->FirstPropertyToInit; LocalProp != NULL; LocalProp = (FProperty*)(LocalProp->Next))
|
|
{
|
|
LocalProp->InitializeValue_InContainer(NewStack.Locals);
|
|
}
|
|
}
|
|
|
|
if (Function->Script.Num() > 0)
|
|
{
|
|
// Execute the code.
|
|
ExecFtor(Context, NewStack, RESULT_PARAM);
|
|
}
|
|
|
|
#if USE_UBER_GRAPH_PERSISTENT_FRAME
|
|
Function->GetOuterUClassUnchecked()->PopPersistentUberGraphFrameProperties(FrameMemory, &Stack);
|
|
#endif
|
|
|
|
if (!bUsePersistentFrame)
|
|
{
|
|
// destruct properties on the stack, except for out params since we know we didn't use that memory
|
|
for (FProperty* Destruct = Function->DestructorLink; Destruct; Destruct = Destruct->DestructorLinkNext)
|
|
{
|
|
if (!Destruct->HasAnyPropertyFlags(CPF_OutParm))
|
|
{
|
|
Destruct->DestroyValue_InContainer(NewStack.Locals);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFINE_FUNCTION(UObject::execCallMathFunction)
|
|
{
|
|
UFunction* Function = (UFunction*)Stack.ReadObject();
|
|
checkSlow(Function);
|
|
checkSlow(Function->FunctionFlags & FUNC_Native);
|
|
// ProcessContext is the arbiter of net callspace, so we can't call net functions using this instruction:
|
|
checkSlow(!Function->HasAnyFunctionFlags(FUNC_NetFuncFlags | FUNC_BlueprintAuthorityOnly | FUNC_BlueprintCosmetic | FUNC_NetRequest | FUNC_NetResponse));
|
|
UObject* NewContext = Function->GetOuterUClassUnchecked()->ClassDefaultObject;
|
|
checkSlow(NewContext);
|
|
{
|
|
#if PER_FUNCTION_SCRIPT_STATS
|
|
FScopeCycleCounterUObject FunctionScope(Function);
|
|
#endif // PER_FUNCTION_SCRIPT_STATS
|
|
|
|
// CurrentNativeFunction is used so far only by FLuaContext::InvokeScriptFunction
|
|
// TGuardValue<UFunction*> NativeFuncGuard(Stack.CurrentNativeFunction, Function);
|
|
|
|
FNativeFuncPtr Func = Function->GetNativeFunc();
|
|
checkSlow(Func);
|
|
(*Func)(NewContext, Stack, RESULT_PARAM);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_CallMath, execCallMathFunction);
|
|
|
|
void UObject::CallFunction(FFrame& Stack, RESULT_DECL, UFunction* Function)
|
|
{
|
|
#if PER_FUNCTION_SCRIPT_STATS
|
|
const bool bShouldTrackFunction = Stats::IsThreadCollectingData();
|
|
FScopeCycleCounterUObject FunctionScope(bShouldTrackFunction ? Function : nullptr);
|
|
#endif // PER_FUNCTION_SCRIPT_STATS
|
|
|
|
#if STATS || ENABLE_STATNAMEDEVENTS
|
|
const bool bShouldTrackObject = GVerboseScriptStats && Stats::IsThreadCollectingData();
|
|
FScopeCycleCounterUObject ContextScope(bShouldTrackObject ? this : nullptr);
|
|
#endif
|
|
|
|
checkSlow(Function);
|
|
|
|
if (Function->FunctionFlags & FUNC_Native)
|
|
{
|
|
const bool bNetFunction = Function->HasAnyFunctionFlags(FUNC_NetFuncFlags | FUNC_BlueprintAuthorityOnly | FUNC_BlueprintCosmetic | FUNC_NetRequest | FUNC_NetResponse);
|
|
const int32 FunctionCallspace = bNetFunction ? GetFunctionCallspace(Function, &Stack) : FunctionCallspace::Local;
|
|
|
|
uint8* SavedCode = NULL;
|
|
if (FunctionCallspace & FunctionCallspace::Remote)
|
|
{
|
|
// Call native networkable function.
|
|
uint8* Buffer = (uint8*)FMemory_Alloca(Function->ParmsSize);
|
|
|
|
SavedCode = Stack.Code; // Since this is native, we need to rollback the stack if we are calling both remotely and locally
|
|
|
|
FMemory::Memzero(Buffer, Function->ParmsSize);
|
|
|
|
// Form the RPC parameters.
|
|
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It)
|
|
{
|
|
uint8* CurrentPropAddr = It->ContainerPtrToValuePtr<uint8>(Buffer);
|
|
if (CastField<FBoolProperty>(*It) && It->ArrayDim == 1)
|
|
{
|
|
// we're going to get '1' returned for bools that are set, so we need to manually mask it in to the proper place
|
|
bool bValue = false;
|
|
Stack.Step(Stack.Object, &bValue);
|
|
if (bValue)
|
|
{
|
|
((FBoolProperty*)*It)->SetPropertyValue(CurrentPropAddr, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stack.Step(Stack.Object, CurrentPropAddr);
|
|
}
|
|
}
|
|
checkSlow(*Stack.Code == EX_EndFunctionParms);
|
|
|
|
CallRemoteFunction(Function, Buffer, Stack.OutParms, &Stack);
|
|
}
|
|
|
|
if (FunctionCallspace & FunctionCallspace::Local)
|
|
{
|
|
if (SavedCode)
|
|
{
|
|
Stack.Code = SavedCode;
|
|
}
|
|
|
|
// Call regular native function.
|
|
FScopeCycleCounterUObject NativeContextScope(GVerboseScriptStats ? Stack.Object : nullptr);
|
|
Function->Invoke(this, Stack, RESULT_PARAM);
|
|
}
|
|
else
|
|
{
|
|
// Eat up the remaining parameters in the stream.
|
|
SkipFunction(Stack, RESULT_PARAM, Function);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProcessScriptFunction(this, Function, Stack, RESULT_PARAM, ProcessInternal);
|
|
}
|
|
}
|
|
|
|
/** Helper function to zero the return value in case of a fatal (runaway / infinite recursion) error */
|
|
void ClearReturnValue(FProperty* ReturnProp, RESULT_DECL)
|
|
{
|
|
if (ReturnProp != NULL)
|
|
{
|
|
uint8* Data = (uint8*)RESULT_PARAM;
|
|
for (int32 ArrayIdx = 0; ArrayIdx < ReturnProp->ArrayDim; ArrayIdx++, Data += ReturnProp->ElementSize)
|
|
{
|
|
// destroy old value if necessary
|
|
ReturnProp->DestroyValue(Data);
|
|
|
|
// copy zero value for return property into Result, or default construct as necessary
|
|
ReturnProp->ClearValue(Data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessLocalScriptFunction(UObject* Context, FFrame& Stack, RESULT_DECL)
|
|
{
|
|
UFunction* Function = (UFunction*)Stack.Node;
|
|
// No POD struct can ever be stored in this buffer.
|
|
MS_ALIGN(16)
|
|
uint8 Buffer[MAX_SIMPLE_RETURN_VALUE_SIZE] GCC_ALIGN(16);
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
FBlueprintContextTracker& BpET = FBlueprintContextTracker::Get();
|
|
if (BpET.bRanaway)
|
|
{
|
|
// If we have a return property, return a zeroed value in it, to try and save execution as much as possible
|
|
FProperty* ReturnProp = (Function)->GetReturnProperty();
|
|
ClearReturnValue(ReturnProp, RESULT_PARAM);
|
|
return;
|
|
}
|
|
else if (++BpET.Recurse == GScriptRecurseLimit)
|
|
{
|
|
// If we have a return property, return a zeroed value in it, to try and save execution as much as possible
|
|
FProperty* ReturnProp = (Function)->GetReturnProperty();
|
|
ClearReturnValue(ReturnProp, RESULT_PARAM);
|
|
|
|
// Notify anyone who cares that we've had a fatal error, so we can shut down PIE, etc
|
|
FBlueprintExceptionInfo InfiniteRecursionExceptionInfo(
|
|
EBlueprintExceptionType::InfiniteLoop,
|
|
FText::Format(
|
|
LOCTEXT("InfiniteLoop", "Infinite script recursion ({0} calls) detected - see log for stack trace"),
|
|
FText::AsNumber(GScriptRecurseLimit)));
|
|
FBlueprintCoreDelegates::ThrowScriptException(Context, Stack, InfiniteRecursionExceptionInfo);
|
|
|
|
// This flag prevents repeated warnings of infinite loop, script exception handler
|
|
// is expected to have terminated execution appropriately:
|
|
BpET.bRanaway = true;
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
// Execute the bytecode
|
|
while (*Stack.Code != EX_Return)
|
|
{
|
|
#if DO_BLUEPRINT_GUARD
|
|
if (BpET.Runaway > GMaximumScriptLoopIterations)
|
|
{
|
|
// If we have a return property, return a zeroed value in it, to try and save execution as much as possible
|
|
FProperty* ReturnProp = (Function)->GetReturnProperty();
|
|
ClearReturnValue(ReturnProp, RESULT_PARAM);
|
|
|
|
// Notify anyone who cares that we've had a fatal error, so we can shut down PIE, etc
|
|
FBlueprintExceptionInfo RunawayLoopExceptionInfo(
|
|
EBlueprintExceptionType::InfiniteLoop,
|
|
FText::Format(
|
|
LOCTEXT("RunawayLoop", "Runaway loop detected (over {0} iterations) - see log for stack trace"),
|
|
FText::AsNumber(GMaximumScriptLoopIterations)));
|
|
|
|
// Need to reset Runaway counter BEFORE throwing script exception, because the exception causes a modal dialog,
|
|
// and other scripts running will then erroneously think they are also "runaway".
|
|
BpET.Runaway = 0;
|
|
|
|
FBlueprintCoreDelegates::ThrowScriptException(Context, Stack, RunawayLoopExceptionInfo);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
Stack.Step(Stack.Object, Buffer);
|
|
}
|
|
|
|
// Step over the return statement and evaluate the result expression
|
|
Stack.Code++;
|
|
|
|
if (*Stack.Code != EX_Nothing)
|
|
{
|
|
Stack.Step(Stack.Object, RESULT_PARAM);
|
|
}
|
|
else
|
|
{
|
|
Stack.Code++;
|
|
}
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
--BpET.Recurse;
|
|
#endif
|
|
}
|
|
|
|
void ProcessLocalFunction(UObject* Context, UFunction* Fn, FFrame& Stack, RESULT_DECL)
|
|
{
|
|
checkSlow(Fn);
|
|
if (Fn->HasAnyFunctionFlags(FUNC_Native))
|
|
{
|
|
FScopeCycleCounterUObject NativeContextScope(GVerboseScriptStats ? Context : nullptr);
|
|
Fn->Invoke(Context, Stack, RESULT_PARAM);
|
|
}
|
|
else
|
|
{
|
|
#if PER_FUNCTION_SCRIPT_STATS
|
|
const bool bShouldTrackFunction = Stats::IsThreadCollectingData();
|
|
FScopeCycleCounterUObject FunctionScope(bShouldTrackFunction ? Fn : nullptr);
|
|
#endif // PER_FUNCTION_SCRIPT_STATS
|
|
ProcessScriptFunction(Context, Fn, Stack, RESULT_PARAM, ProcessLocalScriptFunction);
|
|
}
|
|
}
|
|
|
|
DEFINE_FUNCTION(UObject::ProcessInternal)
|
|
{
|
|
#if DO_BLUEPRINT_GUARD
|
|
// remove later when stable
|
|
if (P_THIS->GetClass()->HasAnyClassFlags(CLASS_NewerVersionExists))
|
|
{
|
|
if (!GIsReinstancing)
|
|
{
|
|
ensureMsgf(!P_THIS->GetClass()->HasAnyClassFlags(CLASS_NewerVersionExists), TEXT("Object '%s' is being used for execution, but its class is out of date and has been replaced with a recompiled class!"), *P_THIS->GetFullName());
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
UFunction* Function = (UFunction*)Stack.Node;
|
|
int32 FunctionCallspace = P_THIS->GetFunctionCallspace(Function, NULL);
|
|
if (FunctionCallspace & FunctionCallspace::Remote)
|
|
{
|
|
P_THIS->CallRemoteFunction(Function, Stack.Locals, Stack.OutParms, NULL);
|
|
}
|
|
|
|
if (FunctionCallspace & FunctionCallspace::Local)
|
|
{
|
|
ProcessLocalScriptFunction(Context, Stack, RESULT_PARAM);
|
|
}
|
|
else
|
|
{
|
|
FProperty* ReturnProp = (Function)->GetReturnProperty();
|
|
ClearReturnValue(ReturnProp, RESULT_PARAM);
|
|
}
|
|
}
|
|
|
|
bool UObject::CallFunctionByNameWithArguments(const TCHAR* Str, FOutputDevice& Ar, UObject* Executor, bool bForceCallWithNonExec /*=false*/)
|
|
{
|
|
// Find an exec function.
|
|
FString MsgStr;
|
|
if (!FParse::Token(Str, MsgStr, true))
|
|
{
|
|
UE_LOG(LogScriptCore, Verbose, TEXT("CallFunctionByNameWithArguments: Not Parsed '%s'"), Str);
|
|
return false;
|
|
}
|
|
const FName Message = FName(*MsgStr, FNAME_Find);
|
|
if (Message == NAME_None)
|
|
{
|
|
UE_LOG(LogScriptCore, Verbose, TEXT("CallFunctionByNameWithArguments: Name not found '%s'"), Str);
|
|
return false;
|
|
}
|
|
UFunction* Function = FindFunction(Message);
|
|
if (nullptr == Function)
|
|
{
|
|
UE_LOG(LogScriptCore, Verbose, TEXT("CallFunctionByNameWithArguments: Function not found '%s'"), Str);
|
|
return false;
|
|
}
|
|
if (0 == (Function->FunctionFlags & FUNC_Exec) && !bForceCallWithNonExec)
|
|
{
|
|
UE_LOG(LogScriptCore, Verbose, TEXT("CallFunctionByNameWithArguments: Function not executable '%s'"), Str);
|
|
return false;
|
|
}
|
|
|
|
FProperty* LastParameter = nullptr;
|
|
|
|
// find the last parameter
|
|
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It)
|
|
{
|
|
LastParameter = *It;
|
|
}
|
|
|
|
// Parse all function parameters.
|
|
uint8* Parms = (uint8*)FMemory_Alloca(Function->ParmsSize);
|
|
FMemory::Memzero(Parms, Function->ParmsSize);
|
|
|
|
for (TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
|
|
{
|
|
FProperty* LocalProp = *It;
|
|
checkSlow(LocalProp);
|
|
if (!LocalProp->HasAnyPropertyFlags(CPF_ZeroConstructor))
|
|
{
|
|
LocalProp->InitializeValue_InContainer(Parms);
|
|
}
|
|
}
|
|
|
|
const uint32 ExportFlags = PPF_None;
|
|
bool bFailed = 0;
|
|
int32 NumParamsEvaluated = 0;
|
|
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It, NumParamsEvaluated++)
|
|
{
|
|
FProperty* PropertyParam = *It;
|
|
checkSlow(PropertyParam); // Fix static analysis warning
|
|
if (NumParamsEvaluated == 0 && Executor)
|
|
{
|
|
FObjectPropertyBase* Op = CastField<FObjectPropertyBase>(*It);
|
|
if (Op && Executor->IsA(Op->PropertyClass))
|
|
{
|
|
// First parameter is implicit reference to object executing the command.
|
|
Op->SetObjectPropertyValue(Op->ContainerPtrToValuePtr<uint8>(Parms), Executor);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Keep old string around in case we need to pass the whole remaining string
|
|
const TCHAR* RemainingStr = Str;
|
|
|
|
// Parse a new argument out of Str
|
|
FString ArgStr;
|
|
FParse::Token(Str, ArgStr, true);
|
|
|
|
// if ArgStr is empty but we have more params to read parse the function to see if these have defaults, if so set them
|
|
bool bFoundDefault = false;
|
|
bool bFailedImport = true;
|
|
#if WITH_EDITOR
|
|
if (!FCString::Strcmp(*ArgStr, TEXT("")))
|
|
{
|
|
const FName DefaultPropertyKey(*(FString(TEXT("CPP_Default_")) + PropertyParam->GetName()));
|
|
const FString& PropertyDefaultValue = Function->GetMetaData(DefaultPropertyKey);
|
|
if (!PropertyDefaultValue.IsEmpty())
|
|
{
|
|
bFoundDefault = true;
|
|
|
|
const TCHAR* Result = It->ImportText(*PropertyDefaultValue, It->ContainerPtrToValuePtr<uint8>(Parms), ExportFlags, NULL);
|
|
bFailedImport = (Result == nullptr);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!bFoundDefault)
|
|
{
|
|
// if this is the last string property and we have remaining arguments to process, we have to assume that this
|
|
// is a sub-command that will be passed to another exec (like "cheat giveall weapons", for example). Therefore
|
|
// we need to use the whole remaining string as an argument, regardless of quotes, spaces etc.
|
|
if (PropertyParam == LastParameter && PropertyParam->IsA<FStrProperty>() && FCString::Strcmp(Str, TEXT("")) != 0)
|
|
{
|
|
ArgStr = FString(RemainingStr).TrimStart();
|
|
}
|
|
|
|
const TCHAR* Result = It->ImportText(*ArgStr, It->ContainerPtrToValuePtr<uint8>(Parms), ExportFlags, NULL);
|
|
bFailedImport = (Result == nullptr);
|
|
}
|
|
|
|
if (bFailedImport)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("Message"), FText::FromName(Message));
|
|
Arguments.Add(TEXT("PropertyName"), FText::FromName(It->GetFName()));
|
|
Arguments.Add(TEXT("FunctionName"), FText::FromName(Function->GetFName()));
|
|
Ar.Logf(TEXT("%s"), *FText::Format(NSLOCTEXT("Core", "BadProperty", "'{Message}': Bad or missing property '{PropertyName}' when trying to call {FunctionName}"), Arguments).ToString());
|
|
bFailed = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFailed)
|
|
{
|
|
ProcessEvent(Function, Parms);
|
|
}
|
|
|
|
//!!destructframe see also UObject::ProcessEvent
|
|
for (TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
|
|
{
|
|
It->DestroyValue_InContainer(Parms);
|
|
}
|
|
|
|
// Success.
|
|
return true;
|
|
}
|
|
|
|
UFunction* UObject::FindFunction(FName InName) const
|
|
{
|
|
return GetClass()->FindFunctionByName(InName);
|
|
}
|
|
|
|
UFunction* UObject::FindFunctionChecked(FName InName) const
|
|
{
|
|
UFunction* Result = FindFunction(InName);
|
|
if (Result == NULL)
|
|
{
|
|
UE_LOG(LogScriptCore, Fatal, TEXT("Failed to find function %s in %s"), *InName.ToString(), *GetFullName());
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
#if TOTAL_OVERHEAD_SCRIPT_STATS
|
|
void FBlueprintEventTimer::FPausableScopeTimer::Start()
|
|
{
|
|
FPausableScopeTimer*& ActiveTimer = FThreadedTimerManager::Get().ActiveTimer;
|
|
|
|
double CurrentTime = FPlatformTime::Seconds();
|
|
if (ActiveTimer)
|
|
{
|
|
ActiveTimer->Pause(CurrentTime);
|
|
}
|
|
|
|
PreviouslyActiveTimer = ActiveTimer;
|
|
StartTime = CurrentTime;
|
|
TotalTime = 0.0;
|
|
|
|
ActiveTimer = this;
|
|
}
|
|
|
|
double FBlueprintEventTimer::FPausableScopeTimer::Stop()
|
|
{
|
|
if (PreviouslyActiveTimer)
|
|
{
|
|
PreviouslyActiveTimer->Resume();
|
|
}
|
|
FThreadedTimerManager::Get().ActiveTimer = PreviouslyActiveTimer;
|
|
return TotalTime + (FPlatformTime::Seconds() - StartTime);
|
|
}
|
|
|
|
FBlueprintEventTimer::FScopedVMTimer::FScopedVMTimer()
|
|
: Timer(), VMParent(nullptr)
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
FScopedVMTimer*& ActiveVMTimer = FThreadedTimerManager::Get().ActiveVMScope;
|
|
VMParent = ActiveVMTimer;
|
|
|
|
ActiveVMTimer = this;
|
|
Timer.Start();
|
|
}
|
|
}
|
|
|
|
FBlueprintEventTimer::FScopedVMTimer::~FScopedVMTimer()
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
INC_FLOAT_STAT_BY(STAT_ScriptVmTime_Total, Timer.Stop() * 1000.0);
|
|
FThreadedTimerManager::Get().ActiveVMScope = VMParent;
|
|
}
|
|
}
|
|
|
|
FBlueprintEventTimer::FScopedNativeTimer::FScopedNativeTimer()
|
|
: Timer()
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
Timer.Start();
|
|
}
|
|
}
|
|
|
|
FBlueprintEventTimer::FScopedNativeTimer::~FScopedNativeTimer()
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
if (FThreadedTimerManager::Get().ActiveVMScope)
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
INC_FLOAT_STAT_BY(STAT_ScriptNativeTime_Total, Timer.Stop() * 1000.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if SCRIPT_AUDIT_ROUTINES
|
|
|
|
// heap would be more time efficient:
|
|
template <typename T>
|
|
void NBest(TArray<T>& OutBest, const T& NewEntry, TFunctionRef<bool(const T&, const T&)> IsBetter)
|
|
{
|
|
if (IsBetter(NewEntry, OutBest.Last()))
|
|
{
|
|
// find insertion point:
|
|
int32 InsertIdx = INDEX_NONE;
|
|
// O(n):
|
|
for (int32 I = 0; I < OutBest.Num(); ++I)
|
|
{
|
|
if (IsBetter(NewEntry, OutBest[I]))
|
|
{
|
|
InsertIdx = I;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// O(n):
|
|
OutBest.Insert(NewEntry, InsertIdx);
|
|
OutBest.Pop();
|
|
}
|
|
}
|
|
|
|
static void OutputLongestFunctions(FOutputDevice& Ar, int32 Num)
|
|
{
|
|
// max heap would be more efficient
|
|
TArray<UFunction*> LongestFunctions;
|
|
LongestFunctions.AddDefaulted(Num);
|
|
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
UClass* BPGC = *It;
|
|
for (TFieldIterator<UFunction> FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
|
|
{
|
|
UFunction* Fn = *FuncIt;
|
|
int32 LenScript = Fn->Script.Num();
|
|
|
|
NBest<UFunction*>(LongestFunctions, Fn,
|
|
[LenScript](UFunction* A, UFunction* B)
|
|
{
|
|
return B == nullptr || LenScript > B->Script.Num();
|
|
});
|
|
}
|
|
}
|
|
|
|
if (LongestFunctions.Num() == 0)
|
|
{
|
|
Ar.Log(TEXT("No script functions found when looking for longest functions."));
|
|
}
|
|
else
|
|
{
|
|
for (UFunction* Fn: LongestFunctions)
|
|
{
|
|
if (!Fn)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Ar.Logf(TEXT("%s %s %d"), *Fn->GetName(), *Fn->GetOuter()->GetName(), Fn->Script.Num());
|
|
}
|
|
}
|
|
}
|
|
|
|
static void OutputMostFrequentlyCalledFunctions(FOutputDevice& OutputAr, int32 Num)
|
|
{
|
|
// Script serialization is recursive and requires certain symbols (e.g. Script, a reference
|
|
// to the bytecode), so we declare a type so that we have some scope:
|
|
struct FCallFrequencyCounter
|
|
{
|
|
FCallFrequencyCounter(TArray<uint8>& InScript)
|
|
: Script(InScript)
|
|
{
|
|
}
|
|
|
|
TArray<uint8>& Script;
|
|
TMap<UFunction*, int32>* FunctionCallCounts = nullptr;
|
|
// Could try and get more context on vcalls, but for
|
|
// this macro auditing tool name should be enough:
|
|
TMap<FName, int32>* VirtualFunctionCallCounts = nullptr;
|
|
|
|
void* GetLinker() { return nullptr; }
|
|
|
|
EExprToken SerializeExpr(int32& iCode, FArchive& Ar)
|
|
{
|
|
#define SERIALIZEEXPR_INC
|
|
#define SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS
|
|
|
|
if (iCode < Script.Num())
|
|
{
|
|
switch ((EExprToken)Script[iCode])
|
|
{
|
|
case EX_CallMath:
|
|
case EX_LocalFinalFunction:
|
|
case EX_FinalFunction: {
|
|
// peak UFunction*:
|
|
if (FunctionCallCounts)
|
|
{
|
|
UFunction* Fn = nullptr;
|
|
FMemory::Memcpy(&Fn, &Script[iCode + 1], sizeof(UFunction*));
|
|
if (ensure(Fn))
|
|
{
|
|
check(Fn->IsValidLowLevel());
|
|
FunctionCallCounts->FindOrAdd(Fn)++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case EX_VirtualFunction:
|
|
case EX_LocalVirtualFunction: {
|
|
// peak function name:
|
|
if (VirtualFunctionCallCounts)
|
|
{
|
|
FScriptName ScriptName;
|
|
FMemory::Memcpy(&ScriptName, &Script[iCode + 1], sizeof(FScriptName));
|
|
VirtualFunctionCallCounts->FindOrAdd(ScriptNameToName(ScriptName))++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#include "UObject/ScriptSerialization.h"
|
|
return Expr;
|
|
#undef SERIALIZEEXPR_INC
|
|
#undef SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS
|
|
}
|
|
|
|
void CountCalls(TMap<UFunction*, int32>* InFunctionCallCounts, TMap<FName, int32>* InVirtualFunctionCallCounts)
|
|
{
|
|
FunctionCallCounts = InFunctionCallCounts;
|
|
VirtualFunctionCallCounts = InVirtualFunctionCallCounts;
|
|
|
|
int32 iCode = 0;
|
|
const int32 ScriptSizeBytes = Script.Num();
|
|
FArchive DummyArchive;
|
|
|
|
while (iCode < ScriptSizeBytes)
|
|
{
|
|
SerializeExpr(iCode, DummyArchive);
|
|
}
|
|
}
|
|
};
|
|
|
|
TMap<UFunction*, int32> FunctionCallCounts;
|
|
TMap<FName, int32> VirtualFunctionCallCounts;
|
|
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
UClass* BPGC = *It;
|
|
for (TFieldIterator<UFunction> FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
|
|
{
|
|
UFunction* Fn = *FuncIt;
|
|
|
|
// disassem and log function calls:
|
|
FCallFrequencyCounter Counter(Fn->Script);
|
|
Counter.CountCalls(&FunctionCallCounts, &VirtualFunctionCallCounts);
|
|
}
|
|
}
|
|
|
|
// sort by # calls:
|
|
{
|
|
TArray<TPair<UFunction*, int32>> FunctionCallsSorted;
|
|
FunctionCallsSorted.AddDefaulted(Num);
|
|
for (const TPair<UFunction*, int32>& Calls: FunctionCallCounts)
|
|
{
|
|
NBest<TPair<UFunction*, int32>>(FunctionCallsSorted, Calls,
|
|
[](const TPair<UFunction*, int32>& A, const TPair<UFunction*, int32>& B) -> bool
|
|
{
|
|
return B.Key == nullptr || A.Value > B.Value;
|
|
});
|
|
}
|
|
|
|
if (FunctionCallsSorted.Num())
|
|
{
|
|
OutputAr.Logf(TEXT("Top %d function call targets"), FunctionCallsSorted.Num());
|
|
for (TPair<UFunction*, int32>& Calls: FunctionCallsSorted)
|
|
{
|
|
if (Calls.Key == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
|
|
OutputAr.Logf(TEXT("%s %s %d"), *Calls.Key->GetName(), *Calls.Key->GetOuter()->GetName(), Calls.Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutputAr.Log(TEXT("No function call instructions found in memory"));
|
|
}
|
|
}
|
|
|
|
{
|
|
TArray<TPair<FName, int32>> VirtualFunctionCallsSorted;
|
|
VirtualFunctionCallsSorted.AddDefaulted(Num);
|
|
for (const TPair<FName, int32>& Calls: VirtualFunctionCallCounts)
|
|
{
|
|
NBest<TPair<FName, int32>>(VirtualFunctionCallsSorted, Calls,
|
|
[](const TPair<FName, int32>& A, const TPair<FName, int32>& B) -> bool
|
|
{
|
|
return B.Key == FName() || A.Value > B.Value;
|
|
});
|
|
}
|
|
|
|
if (VirtualFunctionCallsSorted.Num())
|
|
{
|
|
OutputAr.Logf(TEXT("Top %d virtual function call targets"), VirtualFunctionCallsSorted.Num());
|
|
for (TPair<FName, int32>& Calls: VirtualFunctionCallsSorted)
|
|
{
|
|
if (Calls.Key == FName())
|
|
{
|
|
break;
|
|
}
|
|
|
|
OutputAr.Logf(TEXT("%s %d"), *(Calls.Key.ToString()), Calls.Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutputAr.Log(TEXT("No virtual function call instructions in memory"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void OutputMostFrequentlyUsedInstructions(FOutputDevice& OutputAr, int32 Num)
|
|
{
|
|
// Script serialization is recursive and requires certain symbols (e.g. Script, a reference
|
|
// to the bytecode), so we declare a type so that we have some scope:
|
|
struct FInstructionFrequencyCounter
|
|
{
|
|
FInstructionFrequencyCounter(TArray<uint8>& InScript)
|
|
: Script(InScript)
|
|
{
|
|
}
|
|
|
|
TArray<uint8>& Script;
|
|
TMap<EExprToken, int32>* InstructionCallCounts;
|
|
|
|
void* GetLinker() { return nullptr; }
|
|
|
|
EExprToken SerializeExpr(int32& iCode, FArchive& Ar)
|
|
{
|
|
#define SERIALIZEEXPR_INC
|
|
#define SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS
|
|
|
|
if (iCode < Script.Num())
|
|
{
|
|
if (InstructionCallCounts)
|
|
{
|
|
InstructionCallCounts->FindOrAdd((EExprToken)Script[iCode])++;
|
|
}
|
|
}
|
|
|
|
#include "UObject/ScriptSerialization.h"
|
|
return Expr;
|
|
#undef SERIALIZEEXPR_INC
|
|
#undef SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS
|
|
}
|
|
|
|
void CountInstructions(TMap<EExprToken, int32>* InInstructionCallCounts)
|
|
{
|
|
InstructionCallCounts = InInstructionCallCounts;
|
|
|
|
int32 iCode = 0;
|
|
const int32 ScriptSizeBytes = Script.Num();
|
|
FArchive DummyArchive;
|
|
|
|
while (iCode < ScriptSizeBytes)
|
|
{
|
|
SerializeExpr(iCode, DummyArchive);
|
|
}
|
|
}
|
|
};
|
|
|
|
TMap<EExprToken, int32> InstructionCallCounts;
|
|
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
UClass* BPGC = *It;
|
|
for (TFieldIterator<UFunction> FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
|
|
{
|
|
UFunction* Fn = *FuncIt;
|
|
|
|
// disassem and log function calls:
|
|
FInstructionFrequencyCounter Counter(Fn->Script);
|
|
Counter.CountInstructions(&InstructionCallCounts);
|
|
}
|
|
}
|
|
|
|
// sort by #:
|
|
{
|
|
TArray<TPair<EExprToken, int32>> InstructionCountsSorted;
|
|
InstructionCountsSorted.AddDefaulted(Num);
|
|
|
|
for (const TPair<EExprToken, int32>& Instruction: InstructionCallCounts)
|
|
{
|
|
NBest<TPair<EExprToken, int32>>(InstructionCountsSorted, Instruction,
|
|
[](const TPair<EExprToken, int32>& A, const TPair<EExprToken, int32>& B) -> bool
|
|
{
|
|
return A.Value > B.Value;
|
|
});
|
|
}
|
|
|
|
if (InstructionCountsSorted.Num())
|
|
{
|
|
OutputAr.Logf(TEXT("Top %d bytecode instructions"), InstructionCountsSorted.Num());
|
|
for (TPair<EExprToken, int32>& Instruction: InstructionCountsSorted)
|
|
{
|
|
if (Instruction.Value == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
#if STORE_INSTRUCTION_NAMES
|
|
if (GNativeFuncNames[Instruction.Key])
|
|
{
|
|
FString AsString = GNativeFuncNames[Instruction.Key];
|
|
OutputAr.Logf(TEXT("%s %d"), *AsString, Instruction.Value);
|
|
}
|
|
else
|
|
{
|
|
OutputAr.Logf(TEXT("0x%x %d"), Instruction.Key, Instruction.Value);
|
|
}
|
|
#else
|
|
OutputAr.Logf(TEXT("0x%x %d"), Instruction.Key, Instruction.Value);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutputAr.Log(TEXT("No instructions found in memory"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void OutputTotalBytecodeSize(FOutputDevice& Ar)
|
|
{
|
|
uint32 TotalSize = 0;
|
|
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
UClass* BPGC = *It;
|
|
for (TFieldIterator<UFunction> FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
|
|
{
|
|
UFunction* Fn = *FuncIt;
|
|
|
|
TotalSize += Fn->Script.Num();
|
|
}
|
|
}
|
|
|
|
Ar.Logf(TEXT("Total bytecode size: %d"), TotalSize);
|
|
}
|
|
|
|
struct FScriptAuditExec
|
|
: public FSelfRegisteringExec
|
|
{
|
|
// FSelfRegisteringExec:
|
|
virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override;
|
|
} ScriptAudit;
|
|
|
|
bool FScriptAuditExec::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
|
|
{
|
|
if (FParse::Command(&Cmd, TEXT("ScriptAudit")))
|
|
{
|
|
FString ParsedCommand = FParse::Token(Cmd, 0);
|
|
|
|
if (ParsedCommand.Equals(TEXT("LongestFunctions"), ESearchCase::IgnoreCase))
|
|
{
|
|
int32 NumToOutput = 20;
|
|
|
|
FString Num = FParse::Token(Cmd, 0);
|
|
if (!Num.IsEmpty())
|
|
{
|
|
NumToOutput = FCString::Atoi(*Num);
|
|
}
|
|
OutputLongestFunctions(Ar, NumToOutput);
|
|
return true;
|
|
}
|
|
else if (ParsedCommand.Equals(TEXT("FrequentFunctionsCalled"), ESearchCase::IgnoreCase))
|
|
{
|
|
int32 NumToOutput = 20;
|
|
|
|
FString Num = FParse::Token(Cmd, 0);
|
|
if (!Num.IsEmpty())
|
|
{
|
|
NumToOutput = FCString::Atoi(*Num);
|
|
}
|
|
OutputMostFrequentlyCalledFunctions(Ar, NumToOutput);
|
|
return true;
|
|
}
|
|
else if (ParsedCommand.Equals(TEXT("FrequentInstructions"), ESearchCase::IgnoreCase))
|
|
{
|
|
int32 NumToOutput = 20;
|
|
|
|
FString Num = FParse::Token(Cmd, 0);
|
|
if (!Num.IsEmpty())
|
|
{
|
|
NumToOutput = FCString::Atoi(*Num);
|
|
}
|
|
OutputMostFrequentlyUsedInstructions(Ar, NumToOutput);
|
|
return true;
|
|
}
|
|
else if (ParsedCommand.Equals(TEXT("TotalBytecodeSize"), ESearchCase::IgnoreCase))
|
|
{
|
|
OutputTotalBytecodeSize(Ar);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endif // SCRIPT_AUDIT_ROUTINES
|
|
|
|
// Switch for a lightweight process event counter, useful when disabling the blueprint guard
|
|
// which can taint profiling results:
|
|
#define LIGHTWEIGHT_PROCESS_EVENT_COUNTER 0 && !DO_BLUEPRINT_GUARD
|
|
|
|
#if LIGHTWEIGHT_PROCESS_EVENT_COUNTER || PER_FUNCTION_SCRIPT_STATS
|
|
thread_local int32 ProcessEventCounter = 0;
|
|
#endif
|
|
|
|
void UObject::ProcessEvent(UFunction* Function, void* Parms)
|
|
{
|
|
checkf(!IsUnreachable(), TEXT("%s Function: '%s'"), *GetFullName(), *Function->GetPathName());
|
|
checkf(!FUObjectThreadContext::Get().IsRoutingPostLoad, TEXT("Cannot call UnrealScript (%s - %s) while PostLoading objects"), *GetFullName(), *Function->GetFullName());
|
|
|
|
#if TOTAL_OVERHEAD_SCRIPT_STATS
|
|
FBlueprintEventTimer::FScopedVMTimer VMTime;
|
|
#endif // TOTAL_OVERHEAD_SCRIPT_STATS
|
|
|
|
// Reject.
|
|
if (IsPendingKill())
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// Cannot invoke script events when the game thread is paused for debugging.
|
|
if (GIntraFrameDebuggingGameThread)
|
|
{
|
|
if (GFirstFrameIntraFrameDebugging)
|
|
{
|
|
UE_LOG(LogScriptCore, Warning, TEXT("Cannot call UnrealScript (%s - %s) while stopped at a breakpoint."), *GetFullName(), *Function->GetFullName());
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
if ((Function->FunctionFlags & FUNC_Native) != 0)
|
|
{
|
|
int32 FunctionCallspace = GetFunctionCallspace(Function, NULL);
|
|
if (FunctionCallspace & FunctionCallspace::Remote)
|
|
{
|
|
CallRemoteFunction(Function, Parms, NULL, NULL);
|
|
}
|
|
|
|
if ((FunctionCallspace & FunctionCallspace::Local) == 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (Function->Script.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
checkSlow((Function->ParmsSize == 0) || (Parms != NULL));
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
FBlueprintContextTracker& BlueprintContextTracker = FBlueprintContextTracker::Get();
|
|
const int32 ProcessEventDepth = BlueprintContextTracker.GetScriptEntryTag();
|
|
BlueprintContextTracker.EnterScriptContext(this, Function);
|
|
#elif PER_FUNCTION_SCRIPT_STATS || LIGHTWEIGHT_PROCESS_EVENT_COUNTER
|
|
const int32 ProcessEventDepth = ProcessEventCounter;
|
|
TGuardValue<int32> PECounter(ProcessEventCounter, ProcessEventCounter + 1);
|
|
#endif
|
|
|
|
#if PER_FUNCTION_SCRIPT_STATS
|
|
const bool bShouldTrackFunction = (GMaxFunctionStatDepth == -1 || ProcessEventDepth < GMaxFunctionStatDepth) && Stats::IsThreadCollectingData();
|
|
FScopeCycleCounterUObject FunctionScope(bShouldTrackFunction ? Function : nullptr);
|
|
#endif // PER_FUNCTION_SCRIPT_STATS
|
|
|
|
#if STATS || ENABLE_STATNAMEDEVENTS
|
|
const bool bShouldTrackObject = GVerboseScriptStats && Stats::IsThreadCollectingData();
|
|
FScopeCycleCounterUObject ContextScope(bShouldTrackObject ? this : nullptr);
|
|
#endif
|
|
|
|
#if LIGHTWEIGHT_PROCESS_EVENT_COUNTER
|
|
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_BlueprintTime, IsInGameThread() && ProcessEventCounter == 1);
|
|
#endif
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
// Only start stat if this is the top level context
|
|
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_BlueprintTime, IsInGameThread() && BlueprintContextTracker.GetScriptEntryTag() == 1);
|
|
#endif
|
|
|
|
#if UE_BLUEPRINT_EVENTGRAPH_FASTCALLS
|
|
// Fast path for ubergraph calls
|
|
int32 EventGraphParams;
|
|
if (Function->EventGraphFunction != nullptr)
|
|
{
|
|
// Call directly into the event graph, skipping the stub thunk function
|
|
EventGraphParams = Function->EventGraphCallOffset;
|
|
Parms = &EventGraphParams;
|
|
Function = Function->EventGraphFunction;
|
|
|
|
// Validate assumptions required for this optimized path (EventGraphFunction should have only been filled out if these held)
|
|
checkSlow(Function->ParmsSize == sizeof(EventGraphParams));
|
|
checkSlow(Function->FirstPropertyToInit == nullptr);
|
|
checkSlow(Function->PostConstructLink == nullptr);
|
|
}
|
|
#endif
|
|
|
|
// Scope required for scoped script stats.
|
|
{
|
|
uint8* Frame = NULL;
|
|
#if USE_UBER_GRAPH_PERSISTENT_FRAME
|
|
if (Function->HasAnyFunctionFlags(FUNC_UbergraphFunction))
|
|
{
|
|
Frame = Function->GetOuterUClassUnchecked()->GetPersistentUberGraphFrame(this, Function);
|
|
}
|
|
#endif
|
|
const bool bUsePersistentFrame = (NULL != Frame);
|
|
if (!bUsePersistentFrame)
|
|
{
|
|
Frame = (uint8*)FMemory_Alloca(Function->PropertiesSize);
|
|
// zero the local property memory
|
|
FMemory::Memzero(Frame + Function->ParmsSize, Function->PropertiesSize - Function->ParmsSize);
|
|
}
|
|
|
|
// initialize the parameter properties
|
|
FMemory::Memcpy(Frame, Parms, Function->ParmsSize);
|
|
|
|
// Create a new local execution stack.
|
|
FFrame NewStack(this, Function, Frame, NULL, Function->ChildProperties);
|
|
|
|
checkSlow(NewStack.Locals || Function->ParmsSize == 0);
|
|
|
|
// if the function has out parameters, fill the stack frame's out parameter info with the info for those params
|
|
if (Function->HasAnyFunctionFlags(FUNC_HasOutParms))
|
|
{
|
|
FOutParmRec** LastOut = &NewStack.OutParms;
|
|
for (FProperty* Property = (FProperty*)(Function->ChildProperties); Property && (Property->PropertyFlags & (CPF_Parm)) == CPF_Parm; Property = (FProperty*)Property->Next)
|
|
{
|
|
// this is used for optional parameters - the destination address for out parameter values is the address of the calling function
|
|
// so we'll need to know which address to use if we need to evaluate the default parm value expression located in the new function's
|
|
// bytecode
|
|
if (Property->HasAnyPropertyFlags(CPF_OutParm))
|
|
{
|
|
CA_SUPPRESS(6263)
|
|
FOutParmRec* Out = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec));
|
|
// set the address and property in the out param info
|
|
// note that since C++ doesn't support "optional out" we can ignore that here
|
|
Out->PropAddr = Property->ContainerPtrToValuePtr<uint8>(Parms);
|
|
Out->Property = Property;
|
|
|
|
// add the new out param info to the stack frame's linked list
|
|
if (*LastOut)
|
|
{
|
|
(*LastOut)->NextOutParm = Out;
|
|
LastOut = &(*LastOut)->NextOutParm;
|
|
}
|
|
else
|
|
{
|
|
*LastOut = Out;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the next pointer of the last item to NULL to mark the end of the list
|
|
if (*LastOut)
|
|
{
|
|
(*LastOut)->NextOutParm = NULL;
|
|
}
|
|
}
|
|
|
|
if (!bUsePersistentFrame)
|
|
{
|
|
for (FProperty* LocalProp = Function->FirstPropertyToInit; LocalProp != NULL; LocalProp = (FProperty*)LocalProp->Next)
|
|
{
|
|
LocalProp->InitializeValue_InContainer(NewStack.Locals);
|
|
}
|
|
}
|
|
|
|
// Call native function or UObject::ProcessInternal.
|
|
const bool bHasReturnParam = Function->ReturnValueOffset != MAX_uint16;
|
|
uint8* ReturnValueAddress = bHasReturnParam ? ((uint8*)Parms + Function->ReturnValueOffset) : nullptr;
|
|
Function->Invoke(this, NewStack, ReturnValueAddress);
|
|
|
|
if (!bUsePersistentFrame)
|
|
{
|
|
// Destroy local variables except function parameters.!! see also UObject::CallFunctionByNameWithArguments
|
|
// also copy back constructed value parms here so the correct copy is destroyed when the event function returns
|
|
for (FProperty* P = Function->DestructorLink; P; P = P->DestructorLinkNext)
|
|
{
|
|
if (!P->IsInContainer(Function->ParmsSize))
|
|
{
|
|
P->DestroyValue_InContainer(NewStack.Locals);
|
|
}
|
|
else if (!(P->PropertyFlags & CPF_OutParm))
|
|
{
|
|
FMemory::Memcpy(P->ContainerPtrToValuePtr<uint8>(Parms), P->ContainerPtrToValuePtr<uint8>(NewStack.Locals), P->ArrayDim * P->ElementSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DO_BLUEPRINT_GUARD
|
|
BlueprintContextTracker.ExitScriptContext();
|
|
#endif
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
DEFINE_FUNCTION(UObject::execUndefined)
|
|
{
|
|
Stack.Logf(ELogVerbosity::Error, TEXT("Unknown code token %02X"), Stack.Code[-1]);
|
|
}
|
|
|
|
DEFINE_FUNCTION(UObject::execLocalVariable)
|
|
{
|
|
checkSlow(Stack.Object == P_THIS);
|
|
checkSlow(Stack.Locals != NULL);
|
|
|
|
FProperty* VarProperty = Stack.ReadProperty();
|
|
if (VarProperty == nullptr)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, LOCTEXT("MissingLocalVariable", "Attempted to access missing local variable. If this is a packaged/cooked build, are you attempting to use an editor-only property?"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
}
|
|
else
|
|
{
|
|
Stack.MostRecentPropertyAddress = VarProperty->ContainerPtrToValuePtr<uint8>(Stack.Locals);
|
|
|
|
if (RESULT_PARAM)
|
|
{
|
|
VarProperty->CopyCompleteValueToScriptVM(RESULT_PARAM, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LocalVariable, execLocalVariable);
|
|
|
|
DEFINE_FUNCTION(UObject::execInstanceVariable)
|
|
{
|
|
FProperty* VarProperty = (FProperty*)Stack.ReadObject();
|
|
Stack.MostRecentProperty = VarProperty;
|
|
|
|
if (VarProperty == nullptr || !P_THIS->IsA((UClass*)VarProperty->InternalGetOwnerAsUObjectUnsafe()))
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, FText::Format(LOCTEXT("MissingProperty", "Attempted to access missing property '{0}'. If this is a packaged/cooked build, are you attempting to use an editor-only property?"), FText::FromString(GetNameSafe(VarProperty))));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
}
|
|
else
|
|
{
|
|
Stack.MostRecentPropertyAddress = VarProperty->ContainerPtrToValuePtr<uint8>(P_THIS);
|
|
|
|
if (RESULT_PARAM)
|
|
{
|
|
VarProperty->CopyCompleteValueToScriptVM(RESULT_PARAM, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_InstanceVariable, execInstanceVariable);
|
|
|
|
DEFINE_FUNCTION(UObject::execClassSparseDataVariable)
|
|
{
|
|
FProperty* VarProperty = (FProperty*)Stack.ReadObject();
|
|
Stack.MostRecentProperty = VarProperty;
|
|
|
|
if (VarProperty == nullptr || P_THIS->GetSparseClassDataStruct() == nullptr)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, FText::Format(LOCTEXT("MissingSparseProperty", "Attempted to access missing sparse property '{0}' {1}, {2}. If this is a packaged/cooked build, are you attempting to use an editor-only property?"), FText::FromString(GetNameSafe(VarProperty)), FText::FromString(GetNameSafe(P_THIS->GetSparseClassDataStruct())), FText::FromString(GetNameSafe(VarProperty ? VarProperty->GetOwner<UClass>() : nullptr))));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
}
|
|
else
|
|
{
|
|
void* SparseDataBaseAddress = P_THIS->GetClass()->GetOrCreateSparseClassData();
|
|
Stack.MostRecentPropertyAddress = VarProperty->ContainerPtrToValuePtr<uint8>(SparseDataBaseAddress);
|
|
|
|
// SPARSEDATA_TODO: remove these two lines once we're sure the math is right
|
|
int32 Offset = VarProperty->GetOffset_ForInternal();
|
|
check((uint8*)SparseDataBaseAddress + Offset == Stack.MostRecentPropertyAddress);
|
|
|
|
if (RESULT_PARAM)
|
|
{
|
|
VarProperty->CopyCompleteValueToScriptVM(RESULT_PARAM, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ClassSparseDataVariable, execClassSparseDataVariable);
|
|
|
|
DEFINE_FUNCTION(UObject::execDefaultVariable)
|
|
{
|
|
FProperty* VarProperty = (FProperty*)Stack.ReadObject();
|
|
Stack.MostRecentProperty = VarProperty;
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
|
|
UObject* DefaultObject = nullptr;
|
|
if (P_THIS->HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
DefaultObject = P_THIS;
|
|
}
|
|
else
|
|
{
|
|
// @todo - allow access to archetype properties through object references?
|
|
}
|
|
|
|
if (VarProperty == nullptr || (DefaultObject && !DefaultObject->IsA((UClass*)VarProperty->InternalGetOwnerAsUObjectUnsafe())))
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, LOCTEXT("MissingPropertyDefaultObject", "Attempted to access a missing property on a CDO. If this is a packaged/cooked build, are you attempting to use an editor-only property?"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
else
|
|
{
|
|
if (DefaultObject != nullptr)
|
|
{
|
|
Stack.MostRecentPropertyAddress = VarProperty->ContainerPtrToValuePtr<uint8>(DefaultObject);
|
|
if (RESULT_PARAM)
|
|
{
|
|
VarProperty->CopyCompleteValueToScriptVM(RESULT_PARAM, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, LOCTEXT("AccessNoneDefaultObject", "Accessed None attempting to read a default property"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_DefaultVariable, execDefaultVariable);
|
|
|
|
DEFINE_FUNCTION(UObject::execLocalOutVariable)
|
|
{
|
|
checkSlow(Stack.Object == P_THIS);
|
|
|
|
// get the property we need to find
|
|
FProperty* VarProperty = Stack.ReadProperty();
|
|
|
|
// look through the out parameter infos and find the one that has the address of this property
|
|
FOutParmRec* Out = Stack.OutParms;
|
|
checkSlow(Out);
|
|
while (Out->Property != VarProperty)
|
|
{
|
|
Out = Out->NextOutParm;
|
|
checkSlow(Out);
|
|
}
|
|
Stack.MostRecentPropertyAddress = Out->PropAddr;
|
|
|
|
// if desired, copy the value in that address to Result
|
|
if (RESULT_PARAM && RESULT_PARAM != Stack.MostRecentPropertyAddress)
|
|
{
|
|
VarProperty->CopyCompleteValueToScriptVM(RESULT_PARAM, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LocalOutVariable, execLocalOutVariable);
|
|
|
|
DEFINE_FUNCTION(UObject::execInterfaceContext)
|
|
{
|
|
// get the value of the interface variable
|
|
FScriptInterface InterfaceValue;
|
|
Stack.Step(P_THIS, &InterfaceValue);
|
|
|
|
if (RESULT_PARAM != NULL)
|
|
{
|
|
// copy the UObject pointer to Result
|
|
*(UObject**)RESULT_PARAM = InterfaceValue.GetObject();
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_InterfaceContext, execInterfaceContext);
|
|
|
|
DEFINE_FUNCTION(UObject::execClassContext)
|
|
{
|
|
// Get class expression.
|
|
UClass* ClassContext = NULL;
|
|
Stack.Step(P_THIS, &ClassContext);
|
|
|
|
// Execute expression in class context.
|
|
if (IsValid(ClassContext))
|
|
{
|
|
UObject* DefaultObject = ClassContext->GetDefaultObject();
|
|
check(DefaultObject != NULL);
|
|
|
|
Stack.Code += sizeof(CodeSkipSizeType) // Code offset for NULL expressions.
|
|
+ sizeof(ScriptPointerType); // Property corresponding to the r-value data, in case the l-value needs to be cleared
|
|
Stack.Step(DefaultObject, RESULT_PARAM);
|
|
}
|
|
else
|
|
{
|
|
if (Stack.MostRecentProperty != NULL)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
FText::Format(
|
|
LOCTEXT("AccessedNoneClass", "Accessed None trying to read Class from property {0}"),
|
|
FText::FromString(Stack.MostRecentProperty->GetName())));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
else
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, LOCTEXT("AccessedNoneClassUnknownProperty", "Accessed None reading a Class"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
|
|
const CodeSkipSizeType wSkip = Stack.ReadCodeSkipCount(); // Code offset for NULL expressions. Code += sizeof(CodeSkipSizeType)
|
|
FProperty* RValueProperty = nullptr;
|
|
const VariableSizeType bSize = Stack.ReadVariableSize(&RValueProperty); // Code += sizeof(ScriptPointerType) + sizeof(uint8)
|
|
Stack.Code += wSkip;
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
|
|
if (RESULT_PARAM && RValueProperty)
|
|
{
|
|
RValueProperty->ClearValue(RESULT_PARAM);
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ClassContext, execClassContext);
|
|
|
|
DEFINE_FUNCTION(UObject::execEndOfScript)
|
|
{
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
UE_LOG(LogScriptCore, Warning, TEXT("--- Dumping bytecode for %s on %s ---"), *Stack.Node->GetFullName(), *Stack.Object->GetFullName());
|
|
const UFunction* Func = Stack.Node;
|
|
for (int32 i = 0; i < Func->Script.Num(); ++i)
|
|
{
|
|
UE_LOG(LogScriptCore, Log, TEXT("0x%x"), Func->Script[i]);
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
UE_LOG(LogScriptCore, Fatal, TEXT("Execution beyond end of script in %s on %s"), *Stack.Node->GetFullName(), *Stack.Object->GetFullName());
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_EndOfScript, execEndOfScript);
|
|
|
|
DEFINE_FUNCTION(UObject::execNothing)
|
|
{
|
|
// Do nothing.
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Nothing, execNothing);
|
|
|
|
DEFINE_FUNCTION(UObject::execNothingOp4a)
|
|
{
|
|
// Do nothing.
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_DeprecatedOp4A, execNothingOp4a);
|
|
|
|
DEFINE_FUNCTION(UObject::execBreakpoint)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (GIsEditor)
|
|
{
|
|
FBlueprintExceptionInfo BreakpointExceptionInfo(EBlueprintExceptionType::Breakpoint);
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, BreakpointExceptionInfo);
|
|
}
|
|
#endif
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Breakpoint, execBreakpoint);
|
|
|
|
DEFINE_FUNCTION(UObject::execTracepoint)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (GIsEditor)
|
|
{
|
|
FBlueprintExceptionInfo TracepointExceptionInfo(EBlueprintExceptionType::Tracepoint);
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, TracepointExceptionInfo);
|
|
}
|
|
#endif
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Tracepoint, execTracepoint);
|
|
|
|
DEFINE_FUNCTION(UObject::execWireTracepoint)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (GIsEditor)
|
|
{
|
|
FBlueprintExceptionInfo TracepointExceptionInfo(EBlueprintExceptionType::WireTracepoint);
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, TracepointExceptionInfo);
|
|
}
|
|
#endif
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_WireTracepoint, execWireTracepoint);
|
|
|
|
DEFINE_FUNCTION(UObject::execInstrumentation)
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
const EScriptInstrumentation::Type EventType = static_cast<EScriptInstrumentation::Type>(Stack.PeekCode());
|
|
#if WITH_EDITORONLY_DATA
|
|
if (GIsEditor)
|
|
{
|
|
if (EventType == EScriptInstrumentation::NodeEntry)
|
|
{
|
|
FBlueprintExceptionInfo TracepointExceptionInfo(EBlueprintExceptionType::Tracepoint);
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, TracepointExceptionInfo);
|
|
}
|
|
else if (EventType == EScriptInstrumentation::NodeExit)
|
|
{
|
|
FBlueprintExceptionInfo WiretraceExceptionInfo(EBlueprintExceptionType::WireTracepoint);
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, WiretraceExceptionInfo);
|
|
}
|
|
else if (EventType == EScriptInstrumentation::NodeDebugSite)
|
|
{
|
|
FBlueprintExceptionInfo TracepointExceptionInfo(EBlueprintExceptionType::Breakpoint);
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, TracepointExceptionInfo);
|
|
}
|
|
}
|
|
#endif
|
|
if (EventType == EScriptInstrumentation::InlineEvent)
|
|
{
|
|
const FName& EventName = *reinterpret_cast<FName*>(&Stack.Code[1]);
|
|
FScriptInstrumentationSignal InstrumentationEventInfo(EventType, P_THIS, Stack, EventName);
|
|
FBlueprintCoreDelegates::InstrumentScriptEvent(InstrumentationEventInfo);
|
|
Stack.SkipCode(sizeof(FName) + 1);
|
|
}
|
|
else
|
|
{
|
|
FScriptInstrumentationSignal InstrumentationEventInfo(EventType, P_THIS, Stack);
|
|
FBlueprintCoreDelegates::InstrumentScriptEvent(InstrumentationEventInfo);
|
|
Stack.SkipCode(1);
|
|
}
|
|
#endif
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_InstrumentationEvent, execInstrumentation);
|
|
|
|
DEFINE_FUNCTION(UObject::execEndFunctionParms)
|
|
{
|
|
// For skipping over optional function parms without values specified.
|
|
Stack.Code--;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_EndFunctionParms, execEndFunctionParms);
|
|
|
|
DEFINE_FUNCTION(UObject::execJump)
|
|
{
|
|
CHECK_RUNAWAY;
|
|
|
|
// Jump immediate.
|
|
CodeSkipSizeType Offset = Stack.ReadCodeSkipCount();
|
|
Stack.Code = &Stack.Node->Script[Offset];
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Jump, execJump);
|
|
|
|
DEFINE_FUNCTION(UObject::execComputedJump)
|
|
{
|
|
CHECK_RUNAWAY;
|
|
|
|
// Get the jump offset expression
|
|
int32 ComputedOffset = 0;
|
|
Stack.Step(Stack.Object, &ComputedOffset);
|
|
check((ComputedOffset < Stack.Node->Script.Num()) && (ComputedOffset >= 0));
|
|
|
|
// Jump to the new offset
|
|
Stack.Code = &Stack.Node->Script[ComputedOffset];
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ComputedJump, execComputedJump);
|
|
|
|
DEFINE_FUNCTION(UObject::execJumpIfNot)
|
|
{
|
|
CHECK_RUNAWAY;
|
|
|
|
// Get code offset.
|
|
CodeSkipSizeType Offset = Stack.ReadCodeSkipCount();
|
|
|
|
// Get boolean test value.
|
|
bool Value = 0;
|
|
Stack.Step(Stack.Object, &Value);
|
|
|
|
// Jump if false.
|
|
if (!Value)
|
|
{
|
|
Stack.Code = &Stack.Node->Script[Offset];
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_JumpIfNot, execJumpIfNot);
|
|
|
|
DEFINE_FUNCTION(UObject::execAssert)
|
|
{
|
|
// Get line number.
|
|
int32 wLine = Stack.ReadWord();
|
|
|
|
// find out whether we are in debug mode and therefore should crash on failure
|
|
uint8 bDebug = *(uint8*)Stack.Code++;
|
|
|
|
// Get boolean assert value.
|
|
uint32 Value = 0;
|
|
Stack.Step(Stack.Object, &Value);
|
|
|
|
// Check it.
|
|
if (!Value)
|
|
{
|
|
Stack.Logf(TEXT("%s"), *Stack.GetStackTrace());
|
|
if (bDebug)
|
|
{
|
|
Stack.Logf(ELogVerbosity::Error, TEXT("Assertion failed, line %i"), wLine);
|
|
}
|
|
else
|
|
{
|
|
UE_SUPPRESS(LogScript, Warning, Stack.Logf(TEXT("Assertion failed, line %i"), wLine));
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Assert, execAssert);
|
|
|
|
DEFINE_FUNCTION(UObject::execPushExecutionFlow)
|
|
{
|
|
// Read a code offset and push it onto the flow stack
|
|
CodeSkipSizeType Offset = Stack.ReadCodeSkipCount();
|
|
Stack.FlowStack.Push(Offset);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_PushExecutionFlow, execPushExecutionFlow);
|
|
|
|
DEFINE_FUNCTION(UObject::execPopExecutionFlow)
|
|
{
|
|
// Since this is a branch function, check for runaway script execution
|
|
CHECK_RUNAWAY;
|
|
|
|
// Try to pop an entry off the stack and go there
|
|
if (Stack.FlowStack.Num())
|
|
{
|
|
CodeSkipSizeType Offset = Stack.FlowStack.Pop(/*bAllowShrinking=*/false);
|
|
Stack.Code = &Stack.Node->Script[Offset];
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogScriptCore, Log, TEXT("%s"), *Stack.GetStackTrace());
|
|
Stack.Logf(ELogVerbosity::Error, TEXT("Tried to pop from an empty flow stack"));
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_PopExecutionFlow, execPopExecutionFlow);
|
|
|
|
DEFINE_FUNCTION(UObject::execPopExecutionFlowIfNot)
|
|
{
|
|
// Since this is a branch function, check for runaway script execution
|
|
CHECK_RUNAWAY;
|
|
|
|
// Get boolean test value.
|
|
bool Value = 0;
|
|
Stack.Step(Stack.Object, &Value);
|
|
|
|
if (!Value)
|
|
{
|
|
// Try to pop an entry off the stack and go there
|
|
if (Stack.FlowStack.Num())
|
|
{
|
|
CodeSkipSizeType Offset = Stack.FlowStack.Pop(/*bAllowShrinking=*/false);
|
|
Stack.Code = &Stack.Node->Script[Offset];
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogScriptCore, Log, TEXT("%s"), *Stack.GetStackTrace());
|
|
Stack.Logf(ELogVerbosity::Error, TEXT("Tried to pop from an empty flow stack"));
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_PopExecutionFlowIfNot, execPopExecutionFlowIfNot);
|
|
|
|
DEFINE_FUNCTION(UObject::execLetValueOnPersistentFrame)
|
|
{
|
|
#if USE_UBER_GRAPH_PERSISTENT_FRAME
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
|
|
FProperty* DestProperty = Stack.ReadProperty();
|
|
checkSlow(DestProperty);
|
|
UFunction* UberGraphFunction = CastChecked<UFunction>(DestProperty->GetOwnerStruct());
|
|
checkSlow(Stack.Object->GetClass()->IsChildOf(UberGraphFunction->GetOuterUClassUnchecked()));
|
|
uint8* FrameBase = UberGraphFunction->GetOuterUClassUnchecked()->GetPersistentUberGraphFrame(Stack.Object, UberGraphFunction);
|
|
checkSlow(FrameBase);
|
|
uint8* DestAddress = DestProperty->ContainerPtrToValuePtr<uint8>(FrameBase);
|
|
if (UberGraphFunction->GetOuterUClassUnchecked()->SupportSavePersistentUberGraphFrameProperties())
|
|
{
|
|
UberGraphFunction->GetOuterUClassUnchecked()->PushPersistentUberGraphFrameProperties(FrameBase, DestProperty, &Stack);
|
|
}
|
|
Stack.Step(Stack.Object, DestAddress);
|
|
|
|
#else
|
|
checkf(false, TEXT("execLetValueOnPersistentFrame: UberGraphPersistentFrame is not supported by current build!"));
|
|
#endif
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LetValueOnPersistentFrame, execLetValueOnPersistentFrame);
|
|
|
|
DEFINE_FUNCTION(UObject::execSwitchValue)
|
|
{
|
|
const int32 NumCases = Stack.ReadWord();
|
|
const CodeSkipSizeType OffsetToEnd = Stack.ReadCodeSkipCount();
|
|
|
|
Stack.MostRecentProperty = nullptr;
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
Stack.Step(Stack.Object, nullptr);
|
|
|
|
FProperty* IndexProperty = Stack.MostRecentProperty;
|
|
checkSlow(IndexProperty);
|
|
|
|
uint8* IndexAdress = Stack.MostRecentPropertyAddress;
|
|
if (!ensure(IndexAdress))
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::NonFatalError,
|
|
FText::Format(
|
|
LOCTEXT("SwitchValueIndex", "Switch statement failed to read property for index value for index property {0}"),
|
|
FText::FromString(IndexProperty->GetName())));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
|
|
bool bProperCaseUsed = false;
|
|
{
|
|
auto LocalTempIndexMem = (uint8*)FMemory_Alloca(IndexProperty->GetSize());
|
|
IndexProperty->InitializeValue(LocalTempIndexMem);
|
|
for (int32 CaseIndex = 0; CaseIndex < NumCases; ++CaseIndex)
|
|
{
|
|
Stack.Step(Stack.Object, LocalTempIndexMem); // case index value
|
|
const CodeSkipSizeType OffsetToNextCase = Stack.ReadCodeSkipCount();
|
|
|
|
if (IndexAdress && IndexProperty->Identical(IndexAdress, LocalTempIndexMem))
|
|
{
|
|
Stack.Step(Stack.Object, RESULT_PARAM);
|
|
bProperCaseUsed = true;
|
|
break;
|
|
}
|
|
|
|
// skip to the next case
|
|
Stack.Code = &Stack.Node->Script[OffsetToNextCase];
|
|
}
|
|
IndexProperty->DestroyValue(LocalTempIndexMem);
|
|
}
|
|
|
|
if (bProperCaseUsed)
|
|
{
|
|
Stack.Code = &Stack.Node->Script[OffsetToEnd];
|
|
}
|
|
else
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::NonFatalError,
|
|
FText::Format(
|
|
LOCTEXT("SwitchValueOutOfBounds", "Switch statement failed to match case for index property {0}"),
|
|
FText::FromString(IndexProperty->GetName())));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
|
|
// get default value
|
|
Stack.Step(Stack.Object, RESULT_PARAM);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SwitchValue, execSwitchValue);
|
|
|
|
DEFINE_FUNCTION(UObject::execArrayGetByRef)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Evaluate variable.
|
|
|
|
if (Stack.MostRecentPropertyAddress == NULL)
|
|
{
|
|
static FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::AccessViolation, LOCTEXT("ArrayGetRefException", "Attempt to assign variable through None"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
|
|
void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
|
FArrayProperty* ArrayProperty = ExactCastField<FArrayProperty>(Stack.MostRecentProperty);
|
|
|
|
int32 ArrayIndex;
|
|
Stack.Step(Stack.Object, &ArrayIndex);
|
|
|
|
if (ArrayProperty == nullptr)
|
|
{
|
|
Stack.bArrayContextFailed = true;
|
|
return;
|
|
}
|
|
|
|
FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayAddr);
|
|
Stack.MostRecentProperty = ArrayProperty->Inner;
|
|
|
|
// Add a little safety for Blueprints to not hard crash
|
|
if (ArrayHelper.IsValidIndex(ArrayIndex))
|
|
{
|
|
Stack.MostRecentPropertyAddress = ArrayHelper.GetRawPtr(ArrayIndex);
|
|
|
|
if (RESULT_PARAM)
|
|
{
|
|
ArrayProperty->Inner->CopyCompleteValueToScriptVM(RESULT_PARAM, ArrayHelper.GetRawPtr(ArrayIndex));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// clear so other methods don't try to use a stale value (depends on this method succeeding)
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
// sometimes other exec functions guard on MostRecentProperty, and expect
|
|
// MostRecentPropertyAddress to be filled out; since this was a failure
|
|
// clear this too (so all reliant execs can properly detect)
|
|
Stack.MostRecentProperty = nullptr;
|
|
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
FText::Format(
|
|
LOCTEXT("ArrayGetOutofBounds", "Attempted to access index {0} from array {1} of length {2}!"),
|
|
FText::AsNumber(ArrayIndex),
|
|
FText::FromString(*ArrayProperty->GetName()),
|
|
FText::AsNumber(ArrayHelper.Num())));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ArrayGetByRef, execArrayGetByRef);
|
|
|
|
DEFINE_FUNCTION(UObject::execLet)
|
|
{
|
|
Stack.MostRecentProperty = nullptr;
|
|
FProperty* LocallyKnownProperty = Stack.ReadPropertyUnchecked();
|
|
|
|
// Get variable address.
|
|
Stack.MostRecentProperty = nullptr;
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
Stack.Step(Stack.Object, nullptr); // Evaluate variable.
|
|
|
|
uint8* LocalTempResult = nullptr;
|
|
if (Stack.MostRecentPropertyAddress == nullptr)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
LOCTEXT("LetAccessNone", "Attempted to assign to None"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
|
|
if (LocallyKnownProperty)
|
|
{
|
|
LocalTempResult = (uint8*)FMemory_Alloca(LocallyKnownProperty->GetSize());
|
|
LocallyKnownProperty->InitializeValue(LocalTempResult);
|
|
Stack.MostRecentPropertyAddress = LocalTempResult;
|
|
}
|
|
else
|
|
{
|
|
Stack.MostRecentPropertyAddress = (uint8*)FMemory_Alloca(1024);
|
|
FMemory::Memzero(Stack.MostRecentPropertyAddress, sizeof(FString));
|
|
}
|
|
}
|
|
|
|
// Evaluate expression into variable.
|
|
Stack.Step(Stack.Object, Stack.MostRecentPropertyAddress);
|
|
|
|
if (LocalTempResult && LocallyKnownProperty)
|
|
{
|
|
LocallyKnownProperty->DestroyValue(LocalTempResult);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Let, execLet);
|
|
|
|
DEFINE_FUNCTION(UObject::execLetObj)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Evaluate variable.
|
|
|
|
if (Stack.MostRecentPropertyAddress == NULL)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
LOCTEXT("LetObjAccessNone", "Accessed None attempting to assign variable on an object"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
|
|
void* ObjAddr = Stack.MostRecentPropertyAddress;
|
|
FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Stack.MostRecentProperty);
|
|
if (ObjectProperty == NULL)
|
|
{
|
|
FArrayProperty* ArrayProp = ExactCastField<FArrayProperty>(Stack.MostRecentProperty);
|
|
if (ArrayProp != NULL)
|
|
{
|
|
ObjectProperty = CastField<FObjectPropertyBase>(ArrayProp->Inner);
|
|
}
|
|
}
|
|
|
|
UObject* NewValue = NULL;
|
|
// evaluate the r-value for this expression into Value
|
|
Stack.Step(Stack.Object, &NewValue);
|
|
|
|
if (ObjAddr)
|
|
{
|
|
checkSlow(ObjectProperty);
|
|
ObjectProperty->SetObjectPropertyValue(ObjAddr, NewValue);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LetObj, execLetObj);
|
|
|
|
DEFINE_FUNCTION(UObject::execLetWeakObjPtr)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Evaluate variable.
|
|
|
|
if (Stack.MostRecentPropertyAddress == NULL)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
LOCTEXT("LetWeakObjAccessNone", "Accessed None attempting to assign variable on a weakly referenced object"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
|
|
void* ObjAddr = Stack.MostRecentPropertyAddress;
|
|
FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Stack.MostRecentProperty);
|
|
if (ObjectProperty == NULL)
|
|
{
|
|
FArrayProperty* ArrayProp = ExactCastField<FArrayProperty>(Stack.MostRecentProperty);
|
|
if (ArrayProp != NULL)
|
|
{
|
|
ObjectProperty = CastField<FObjectPropertyBase>(ArrayProp->Inner);
|
|
}
|
|
}
|
|
|
|
UObject* NewValue = NULL;
|
|
// evaluate the r-value for this expression into Value
|
|
Stack.Step(Stack.Object, &NewValue);
|
|
|
|
if (ObjAddr)
|
|
{
|
|
checkSlow(ObjectProperty);
|
|
ObjectProperty->SetObjectPropertyValue(ObjAddr, NewValue);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LetWeakObjPtr, execLetWeakObjPtr);
|
|
|
|
DEFINE_FUNCTION(UObject::execLetBool)
|
|
{
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
|
|
// Get the variable and address to place the data.
|
|
Stack.Step(Stack.Object, NULL);
|
|
|
|
/*
|
|
Class bool properties are packed together as bitfields, so in order
|
|
to set the value on the correct bool, we need to mask it against
|
|
the bool property's BitMask.
|
|
|
|
Local bool properties (declared inside functions) are not packed, thus
|
|
their bitmask is always 1.
|
|
|
|
Bool properties inside dynamic arrays and tmaps are also not packed together.
|
|
If the bool property we're accessing is an element in a dynamic array, Stack.MostRecentProperty
|
|
will be pointing to the dynamic array that has a FBoolProperty as its inner, so
|
|
we'll need to check for that.
|
|
*/
|
|
uint8* BoolAddr = (uint8*)Stack.MostRecentPropertyAddress;
|
|
FBoolProperty* BoolProperty = ExactCastField<FBoolProperty>(Stack.MostRecentProperty);
|
|
if (BoolProperty == NULL)
|
|
{
|
|
FArrayProperty* ArrayProp = ExactCastField<FArrayProperty>(Stack.MostRecentProperty);
|
|
if (ArrayProp != NULL)
|
|
{
|
|
BoolProperty = ExactCastField<FBoolProperty>(ArrayProp->Inner);
|
|
}
|
|
}
|
|
|
|
bool NewValue = false;
|
|
|
|
// evaluate the r-value for this expression into Value
|
|
Stack.Step(Stack.Object, &NewValue);
|
|
if (BoolAddr)
|
|
{
|
|
checkSlow(CastField<FBoolProperty>(BoolProperty));
|
|
BoolProperty->SetPropertyValue(BoolAddr, NewValue);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LetBool, execLetBool);
|
|
|
|
DEFINE_FUNCTION(UObject::execLetDelegate)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Variable.
|
|
|
|
FScriptDelegate* DelegateAddr = (FScriptDelegate*)Stack.MostRecentPropertyAddress;
|
|
FScriptDelegate Delegate;
|
|
Stack.Step(Stack.Object, &Delegate);
|
|
|
|
if (DelegateAddr != NULL)
|
|
{
|
|
DelegateAddr->BindUFunction(Delegate.GetUObject(), Delegate.GetFunctionName());
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LetDelegate, execLetDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execLetMulticastDelegate)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Variable.
|
|
|
|
FMulticastDelegateProperty* DelegateProp = CastFieldCheckedNullAllowed<FMulticastDelegateProperty>(Stack.MostRecentProperty);
|
|
void* DelegateAddr = Stack.MostRecentPropertyAddress;
|
|
FMulticastScriptDelegate Delegate;
|
|
Stack.Step(Stack.Object, &Delegate);
|
|
|
|
if (DelegateProp && DelegateAddr)
|
|
{
|
|
DelegateProp->SetMulticastDelegate(DelegateAddr, MoveTemp(Delegate));
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LetMulticastDelegate, execLetMulticastDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execSelf)
|
|
{
|
|
// Get Self actor for this context.
|
|
if (RESULT_PARAM != nullptr)
|
|
{
|
|
*(UObject**)RESULT_PARAM = P_THIS;
|
|
}
|
|
// likely it's expecting us to fill out Stack.MostRecentProperty, which you
|
|
// cannot because 'self' is not a FProperty (it is essentially a constant)
|
|
else
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
LOCTEXT("AccessSelfAddress", "Attempted to reference 'self' as an addressable property."));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Self, execSelf);
|
|
|
|
DEFINE_FUNCTION(UObject::execContext)
|
|
{
|
|
P_THIS->ProcessContextOpcode(Stack, RESULT_PARAM, /*bCanFailSilently=*/false);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Context, execContext);
|
|
|
|
DEFINE_FUNCTION(UObject::execContext_FailSilent)
|
|
{
|
|
P_THIS->ProcessContextOpcode(Stack, RESULT_PARAM, /*bCanFailSilently=*/true);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Context_FailSilent, execContext_FailSilent);
|
|
|
|
void UObject::ProcessContextOpcode(FFrame& Stack, RESULT_DECL, bool bCanFailSilently)
|
|
{
|
|
Stack.MostRecentProperty = NULL;
|
|
|
|
// Get object variable.
|
|
UObject* NewContext = NULL;
|
|
Stack.Step(this, &NewContext);
|
|
|
|
uint8* const OriginalCode = Stack.Code;
|
|
const bool bValidContext = IsValid(NewContext);
|
|
// Execute or skip the following expression in the object's context.
|
|
if (bValidContext)
|
|
{
|
|
Stack.Code += sizeof(CodeSkipSizeType) // Code offset for NULL expressions.
|
|
+ sizeof(ScriptPointerType); // Property corresponding to the r-value data, in case the l-value needs to be cleared
|
|
Stack.Step(NewContext, RESULT_PARAM);
|
|
}
|
|
|
|
if (!bValidContext || Stack.bArrayContextFailed)
|
|
{
|
|
if (Stack.bArrayContextFailed)
|
|
{
|
|
Stack.bArrayContextFailed = false;
|
|
Stack.Code = OriginalCode;
|
|
}
|
|
|
|
if (!bCanFailSilently)
|
|
{
|
|
if (NewContext && NewContext->IsPendingKill())
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
FText::Format(
|
|
LOCTEXT("AccessPendingKill", "Attempted to access {0} via property {1}, but {0} is pending kill"),
|
|
FText::FromString(GetNameSafe(NewContext)),
|
|
FText::FromString(GetNameSafe(Stack.MostRecentProperty))));
|
|
FBlueprintCoreDelegates::ThrowScriptException(this, Stack, ExceptionInfo);
|
|
}
|
|
else if (Stack.MostRecentProperty != NULL)
|
|
{
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
FText::Format(
|
|
LOCTEXT("AccessNoneContext", "Accessed None trying to read property {0}"),
|
|
FText::FromString(Stack.MostRecentProperty->GetName())));
|
|
FBlueprintCoreDelegates::ThrowScriptException(this, Stack, ExceptionInfo);
|
|
}
|
|
else
|
|
{
|
|
// Stack.MostRecentProperty will be NULL under the following conditions:
|
|
// 1. the context expression was a function call which returned an object
|
|
// 2. the context expression was a literal object reference
|
|
// 3. the context expression was an instance variable that no longer exists (it was editor-only, etc.)
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
LOCTEXT("AccessNoneNoContext", "Accessed None"));
|
|
FBlueprintCoreDelegates::ThrowScriptException(this, Stack, ExceptionInfo);
|
|
}
|
|
}
|
|
|
|
const CodeSkipSizeType wSkip = Stack.ReadCodeSkipCount(); // Code offset for NULL expressions. Code += sizeof(CodeSkipSizeType)
|
|
FProperty* RValueProperty = nullptr;
|
|
const VariableSizeType bSize = Stack.ReadVariableSize(&RValueProperty); // Code += sizeof(ScriptPointerType) + sizeof(uint8)
|
|
Stack.Code += wSkip;
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
|
|
if (RESULT_PARAM && RValueProperty)
|
|
{
|
|
RValueProperty->ClearValue(RESULT_PARAM);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFINE_FUNCTION(UObject::execStructMemberContext)
|
|
{
|
|
// Get the structure element we care about
|
|
FProperty* StructProperty = Stack.ReadProperty();
|
|
checkSlow(StructProperty);
|
|
|
|
// Evaluate an expression leading to the struct.
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.Step(Stack.Object, NULL);
|
|
|
|
if (Stack.MostRecentProperty != NULL)
|
|
{
|
|
// Offset into the specific member
|
|
Stack.MostRecentPropertyAddress = StructProperty->ContainerPtrToValuePtr<uint8>(Stack.MostRecentPropertyAddress);
|
|
Stack.MostRecentProperty = StructProperty;
|
|
|
|
// Handle variable reads
|
|
if (RESULT_PARAM)
|
|
{
|
|
StructProperty->CopyCompleteValueToScriptVM(RESULT_PARAM, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Access none
|
|
FBlueprintExceptionInfo ExceptionInfo(
|
|
EBlueprintExceptionType::AccessViolation,
|
|
FText::Format(
|
|
LOCTEXT("AccessNoneStructure", "Accessed None reading structure {0}"),
|
|
FText::FromString(StructProperty->GetName())));
|
|
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
|
|
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_StructMemberContext, execStructMemberContext);
|
|
|
|
DEFINE_FUNCTION(UObject::execVirtualFunction)
|
|
{
|
|
// Call the virtual function.
|
|
P_THIS->CallFunction(Stack, RESULT_PARAM, P_THIS->FindFunctionChecked(Stack.ReadName()));
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_VirtualFunction, execVirtualFunction);
|
|
|
|
DEFINE_FUNCTION(UObject::execFinalFunction)
|
|
{
|
|
// Call the final function.
|
|
P_THIS->CallFunction(Stack, RESULT_PARAM, (UFunction*)Stack.ReadObject());
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_FinalFunction, execFinalFunction);
|
|
|
|
DEFINE_FUNCTION(UObject::execLocalVirtualFunction)
|
|
{
|
|
// Call the virtual function.
|
|
ProcessLocalFunction(Context, P_THIS->FindFunctionChecked(Stack.ReadName()), Stack, RESULT_PARAM);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LocalVirtualFunction, execLocalVirtualFunction);
|
|
|
|
DEFINE_FUNCTION(UObject::execLocalFinalFunction)
|
|
{
|
|
// Call the final function.
|
|
ProcessLocalFunction(Context, (UFunction*)Stack.ReadObject(), Stack, RESULT_PARAM);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_LocalFinalFunction, execLocalFinalFunction);
|
|
|
|
class FCallDelegateHelper
|
|
{
|
|
public:
|
|
static void CallMulticastDelegate(FFrame& Stack)
|
|
{
|
|
// Get delegate
|
|
UFunction* SignatureFunction = CastChecked<UFunction>(Stack.ReadObject());
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL);
|
|
FMulticastDelegateProperty* DelegateProp = CastFieldCheckedNullAllowed<FMulticastDelegateProperty>(Stack.MostRecentProperty);
|
|
const FMulticastScriptDelegate* DelegateAddr = (DelegateProp ? DelegateProp->GetMulticastDelegate(Stack.MostRecentPropertyAddress) : nullptr);
|
|
|
|
// Fill parameters
|
|
uint8* Parameters = (uint8*)FMemory_Alloca(SignatureFunction->ParmsSize);
|
|
FMemory::Memzero(Parameters, SignatureFunction->ParmsSize);
|
|
for (FProperty* Property = (FProperty*)SignatureFunction->ChildProperties; *Stack.Code != EX_EndFunctionParms; Property = (FProperty*)Property->Next)
|
|
{
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
if (Property->PropertyFlags & CPF_OutParm)
|
|
{
|
|
Stack.Step(Stack.Object, NULL);
|
|
if (NULL != Stack.MostRecentPropertyAddress)
|
|
{
|
|
check(Property->IsInContainer(SignatureFunction->ParmsSize));
|
|
uint8* ConstRefCopyParamAdress = Property->ContainerPtrToValuePtr<uint8>(Parameters);
|
|
Property->CopyCompleteValueToScriptVM(ConstRefCopyParamAdress, Stack.MostRecentPropertyAddress);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint8* Param = Property->ContainerPtrToValuePtr<uint8>(Parameters);
|
|
checkSlow(Param);
|
|
Property->InitializeValue_InContainer(Parameters);
|
|
Stack.Step(Stack.Object, Param);
|
|
}
|
|
}
|
|
Stack.Code++;
|
|
|
|
// Process delegate
|
|
if (DelegateAddr)
|
|
{
|
|
DelegateAddr->ProcessMulticastDelegate<UObject>(Parameters);
|
|
}
|
|
|
|
// Clean parameters
|
|
for (FProperty* Destruct = SignatureFunction->DestructorLink; Destruct; Destruct = Destruct->DestructorLinkNext)
|
|
{
|
|
Destruct->DestroyValue_InContainer(Parameters);
|
|
}
|
|
}
|
|
};
|
|
|
|
DEFINE_FUNCTION(UObject::execCallMulticastDelegate)
|
|
{
|
|
FCallDelegateHelper::CallMulticastDelegate(Stack);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_CallMulticastDelegate, execCallMulticastDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execAddMulticastDelegate)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Variable.
|
|
|
|
FMulticastDelegateProperty* DelegateProp = CastFieldCheckedNullAllowed<FMulticastDelegateProperty>(Stack.MostRecentProperty);
|
|
void* DelegateAddr = Stack.MostRecentPropertyAddress;
|
|
|
|
FScriptDelegate Delegate;
|
|
Stack.Step(Stack.Object, &Delegate);
|
|
|
|
if (DelegateProp && DelegateAddr)
|
|
{
|
|
DelegateProp->AddDelegate(MoveTemp(Delegate), nullptr, DelegateAddr);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_AddMulticastDelegate, execAddMulticastDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execRemoveMulticastDelegate)
|
|
{
|
|
// Get variable address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Variable.
|
|
|
|
FMulticastDelegateProperty* DelegateProp = CastFieldCheckedNullAllowed<FMulticastDelegateProperty>(Stack.MostRecentProperty);
|
|
void* DelegateAddr = Stack.MostRecentPropertyAddress;
|
|
|
|
FScriptDelegate Delegate;
|
|
Stack.Step(Stack.Object, &Delegate);
|
|
|
|
if (DelegateProp && DelegateAddr)
|
|
{
|
|
DelegateProp->RemoveDelegate(Delegate, nullptr, DelegateAddr);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_RemoveMulticastDelegate, execRemoveMulticastDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execClearMulticastDelegate)
|
|
{
|
|
// Get the delegate address
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL);
|
|
|
|
FMulticastDelegateProperty* DelegateProp = CastFieldCheckedNullAllowed<FMulticastDelegateProperty>(Stack.MostRecentProperty);
|
|
void* DelegateAddr = Stack.MostRecentPropertyAddress;
|
|
|
|
if (DelegateProp && DelegateAddr)
|
|
{
|
|
DelegateProp->ClearDelegate(nullptr, DelegateAddr);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ClearMulticastDelegate, execClearMulticastDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execIntConst)
|
|
{
|
|
*(int32*)RESULT_PARAM = Stack.ReadInt<int32>();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_IntConst, execIntConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execInt64Const)
|
|
{
|
|
*(int64*)RESULT_PARAM = Stack.ReadInt<int64>();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_Int64Const, execInt64Const);
|
|
|
|
DEFINE_FUNCTION(UObject::execUInt64Const)
|
|
{
|
|
*(uint64*)RESULT_PARAM = Stack.ReadInt<uint64>();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_UInt64Const, execUInt64Const);
|
|
|
|
DEFINE_FUNCTION(UObject::execSkipOffsetConst)
|
|
{
|
|
CodeSkipSizeType Literal = Stack.ReadCodeSkipCount();
|
|
*(int32*)RESULT_PARAM = Literal;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SkipOffsetConst, execSkipOffsetConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execFloatConst)
|
|
{
|
|
*(float*)RESULT_PARAM = Stack.ReadFloat();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_FloatConst, execFloatConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execStringConst)
|
|
{
|
|
*(FString*)RESULT_PARAM = (ANSICHAR*)Stack.Code;
|
|
while (*Stack.Code)
|
|
Stack.Code++;
|
|
Stack.Code++;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_StringConst, execStringConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execUnicodeStringConst)
|
|
{
|
|
FString& ResultStr = *(FString*)RESULT_PARAM;
|
|
ResultStr = FString((UCS2CHAR*)Stack.Code);
|
|
|
|
// Inline combine any surrogate pairs in the data when loading into a UTF-32 string
|
|
StringConv::InlineCombineSurrogates(ResultStr);
|
|
|
|
while (*(uint16*)Stack.Code)
|
|
{
|
|
Stack.Code += sizeof(uint16);
|
|
}
|
|
Stack.Code += sizeof(uint16);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_UnicodeStringConst, execUnicodeStringConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execTextConst)
|
|
{
|
|
// What kind of text are we dealing with?
|
|
const EBlueprintTextLiteralType TextLiteralType = (EBlueprintTextLiteralType)*Stack.Code++;
|
|
|
|
switch (TextLiteralType)
|
|
{
|
|
case EBlueprintTextLiteralType::Empty: {
|
|
*(FText*)RESULT_PARAM = FText::GetEmpty();
|
|
}
|
|
break;
|
|
|
|
case EBlueprintTextLiteralType::LocalizedText: {
|
|
FString SourceString;
|
|
Stack.Step(Stack.Object, &SourceString);
|
|
|
|
FString KeyString;
|
|
Stack.Step(Stack.Object, &KeyString);
|
|
|
|
FString Namespace;
|
|
Stack.Step(Stack.Object, &Namespace);
|
|
|
|
*(FText*)RESULT_PARAM = FInternationalization::ForUseOnlyByLocMacroAndGraphNodeTextLiterals_CreateText(*SourceString, *Namespace, *KeyString);
|
|
}
|
|
break;
|
|
|
|
case EBlueprintTextLiteralType::InvariantText: {
|
|
FString SourceString;
|
|
Stack.Step(Stack.Object, &SourceString);
|
|
|
|
*(FText*)RESULT_PARAM = FText::AsCultureInvariant(MoveTemp(SourceString));
|
|
}
|
|
break;
|
|
|
|
case EBlueprintTextLiteralType::LiteralString: {
|
|
FString SourceString;
|
|
Stack.Step(Stack.Object, &SourceString);
|
|
|
|
*(FText*)RESULT_PARAM = FText::FromString(MoveTemp(SourceString));
|
|
}
|
|
break;
|
|
|
|
case EBlueprintTextLiteralType::StringTableEntry: {
|
|
Stack.ReadObject(); // String Table asset (if any)
|
|
|
|
FString TableIdString;
|
|
Stack.Step(Stack.Object, &TableIdString);
|
|
|
|
FString KeyString;
|
|
Stack.Step(Stack.Object, &KeyString);
|
|
|
|
*(FText*)RESULT_PARAM = FText::FromStringTable(FName(*TableIdString), KeyString);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
checkf(false, TEXT("Unknown EBlueprintTextLiteralType! Please update UObject::execTextConst to handle this type of text."));
|
|
break;
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_TextConst, execTextConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execPropertyConst)
|
|
{
|
|
*(FProperty**)RESULT_PARAM = (FProperty*)Stack.ReadObject();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_PropertyConst, execPropertyConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execObjectConst)
|
|
{
|
|
*(UObject**)RESULT_PARAM = (UObject*)Stack.ReadObject();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ObjectConst, execObjectConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execSoftObjectConst)
|
|
{
|
|
FString LongPath;
|
|
Stack.Step(Stack.Object, &LongPath);
|
|
*(FSoftObjectPtr*)RESULT_PARAM = FSoftObjectPath(LongPath);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SoftObjectConst, execSoftObjectConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execFieldPathConst)
|
|
{
|
|
FString StringPath;
|
|
Stack.Step(Stack.Object, &StringPath);
|
|
FFieldPath FieldPath;
|
|
FieldPath.Generate(*StringPath);
|
|
*(FFieldPath*)RESULT_PARAM = FieldPath;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_FieldPathConst, execFieldPathConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execInstanceDelegate)
|
|
{
|
|
FName FunctionName = Stack.ReadName();
|
|
((FScriptDelegate*)RESULT_PARAM)->BindUFunction((FunctionName == NAME_None) ? NULL : P_THIS, FunctionName);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_InstanceDelegate, execInstanceDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execBindDelegate)
|
|
{
|
|
FName FunctionName = Stack.ReadName();
|
|
|
|
// Get delegate address.
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Variable.
|
|
|
|
FScriptDelegate* DelegateAddr = (FScriptDelegate*)Stack.MostRecentPropertyAddress;
|
|
|
|
UObject* ObjectForDelegate = NULL;
|
|
Stack.Step(Stack.Object, &ObjectForDelegate);
|
|
|
|
if (DelegateAddr)
|
|
{
|
|
DelegateAddr->BindUFunction(ObjectForDelegate, FunctionName);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_BindDelegate, execBindDelegate);
|
|
|
|
DEFINE_FUNCTION(UObject::execNameConst)
|
|
{
|
|
*(FName*)RESULT_PARAM = Stack.ReadName();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_NameConst, execNameConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execByteConst)
|
|
{
|
|
*(uint8*)RESULT_PARAM = *Stack.Code++;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ByteConst, execByteConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execRotationConst)
|
|
{
|
|
((FRotator*)RESULT_PARAM)->Pitch = Stack.ReadFloat();
|
|
((FRotator*)RESULT_PARAM)->Yaw = Stack.ReadFloat();
|
|
((FRotator*)RESULT_PARAM)->Roll = Stack.ReadFloat();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_RotationConst, execRotationConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execVectorConst)
|
|
{
|
|
((FVector*)RESULT_PARAM)->X = Stack.ReadFloat();
|
|
((FVector*)RESULT_PARAM)->Y = Stack.ReadFloat();
|
|
((FVector*)RESULT_PARAM)->Z = Stack.ReadFloat();
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_VectorConst, execVectorConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execTransformConst)
|
|
{
|
|
// Rotation
|
|
FQuat TmpRotation;
|
|
TmpRotation.X = Stack.ReadFloat();
|
|
TmpRotation.Y = Stack.ReadFloat();
|
|
TmpRotation.Z = Stack.ReadFloat();
|
|
TmpRotation.W = Stack.ReadFloat();
|
|
|
|
// Translation
|
|
FVector TmpTranslation;
|
|
TmpTranslation.X = Stack.ReadFloat();
|
|
TmpTranslation.Y = Stack.ReadFloat();
|
|
TmpTranslation.Z = Stack.ReadFloat();
|
|
|
|
// Scale
|
|
FVector TmpScale;
|
|
TmpScale.X = Stack.ReadFloat();
|
|
TmpScale.Y = Stack.ReadFloat();
|
|
TmpScale.Z = Stack.ReadFloat();
|
|
|
|
((FTransform*)RESULT_PARAM)->SetComponents(TmpRotation, TmpTranslation, TmpScale);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_TransformConst, execTransformConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execStructConst)
|
|
{
|
|
UScriptStruct* ScriptStruct = CastChecked<UScriptStruct>(Stack.ReadObject());
|
|
int32 SerializedSize = Stack.ReadInt<int32>();
|
|
|
|
// TODO: Change this once structs/classes can be declared as explicitly editor only
|
|
bool bIsEditorOnlyStruct = false;
|
|
|
|
for (FProperty* StructProp = ScriptStruct->PropertyLink; StructProp; StructProp = StructProp->PropertyLinkNext)
|
|
{
|
|
// Skip transient and editor only properties, this needs to be synched with KismetCompilerVMBackend
|
|
if (StructProp->PropertyFlags & CPF_Transient || (!bIsEditorOnlyStruct && StructProp->PropertyFlags & CPF_EditorOnly))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 ArrayIter = 0; ArrayIter < StructProp->ArrayDim; ++ArrayIter)
|
|
{
|
|
Stack.Step(Stack.Object, StructProp->ContainerPtrToValuePtr<uint8>(RESULT_PARAM, ArrayIter));
|
|
}
|
|
}
|
|
|
|
if (ScriptStruct->StructFlags & STRUCT_PostScriptConstruct)
|
|
{
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = ScriptStruct->GetCppStructOps();
|
|
check(TheCppStructOps); // else should not have STRUCT_PostScriptConstruct
|
|
TheCppStructOps->PostScriptConstruct(RESULT_PARAM);
|
|
}
|
|
|
|
P_FINISH; // EX_EndStructConst
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_StructConst, execStructConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execSetArray)
|
|
{
|
|
// Get the array address
|
|
Stack.MostRecentPropertyAddress = NULL;
|
|
Stack.MostRecentProperty = NULL;
|
|
Stack.Step(Stack.Object, NULL); // Array to set
|
|
|
|
FArrayProperty* ArrayProperty = CastFieldChecked<FArrayProperty>(Stack.MostRecentProperty);
|
|
FScriptArrayHelper ArrayHelper(ArrayProperty, Stack.MostRecentPropertyAddress);
|
|
ArrayHelper.EmptyValues();
|
|
|
|
// Read in the parameters one at a time
|
|
int32 i = 0;
|
|
while (*Stack.Code != EX_EndArray)
|
|
{
|
|
ArrayHelper.AddValues(1);
|
|
Stack.Step(Stack.Object, ArrayHelper.GetRawPtr(i++));
|
|
}
|
|
|
|
P_FINISH;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SetArray, execSetArray);
|
|
|
|
DEFINE_FUNCTION(UObject::execSetSet)
|
|
{
|
|
// Get the set address
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
Stack.MostRecentProperty = nullptr;
|
|
Stack.Step(Stack.Object, nullptr); // Set to set
|
|
const int32 Num = Stack.ReadInt<int32>();
|
|
|
|
FSetProperty* SetProperty = CastFieldChecked<FSetProperty>(Stack.MostRecentProperty);
|
|
FScriptSetHelper SetHelper(SetProperty, Stack.MostRecentPropertyAddress);
|
|
SetHelper.EmptyElements(Num);
|
|
|
|
if (Num > 0)
|
|
{
|
|
FDefaultConstructedPropertyElement TempElement(SetProperty->ElementProp);
|
|
|
|
// Read in the parameters one at a time
|
|
while (*Stack.Code != EX_EndSet)
|
|
{
|
|
// needs to be an initialized/constructed value, in case the op is a literal that gets assigned over
|
|
Stack.Step(Stack.Object, TempElement.GetObjAddress());
|
|
SetHelper.AddElement(TempElement.GetObjAddress());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check(*Stack.Code == EX_EndSet);
|
|
}
|
|
|
|
P_FINISH;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SetSet, execSetSet);
|
|
|
|
DEFINE_FUNCTION(UObject::execSetMap)
|
|
{
|
|
// Get the map address
|
|
Stack.MostRecentPropertyAddress = nullptr;
|
|
Stack.MostRecentProperty = nullptr;
|
|
Stack.Step(Stack.Object, nullptr); // Map to set
|
|
const int32 Num = Stack.ReadInt<int32>();
|
|
|
|
FMapProperty* MapProperty = CastFieldChecked<FMapProperty>(Stack.MostRecentProperty);
|
|
FScriptMapHelper MapHelper(MapProperty, Stack.MostRecentPropertyAddress);
|
|
MapHelper.EmptyValues(Num);
|
|
|
|
if (Num > 0)
|
|
{
|
|
FDefaultConstructedPropertyElement TempKey(MapProperty->KeyProp);
|
|
FDefaultConstructedPropertyElement TempValue(MapProperty->ValueProp);
|
|
|
|
// Read in the parameters one at a time
|
|
while (*Stack.Code != EX_EndMap)
|
|
{
|
|
Stack.Step(Stack.Object, TempKey.GetObjAddress());
|
|
Stack.Step(Stack.Object, TempValue.GetObjAddress());
|
|
MapHelper.AddPair(TempKey.GetObjAddress(), TempValue.GetObjAddress());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check(*Stack.Code == EX_EndMap);
|
|
}
|
|
|
|
P_FINISH;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SetMap, execSetMap);
|
|
|
|
DEFINE_FUNCTION(UObject::execArrayConst)
|
|
{
|
|
FProperty* InnerProperty = CastFieldChecked<FProperty>((FField*)Stack.ReadObject());
|
|
int32 Num = Stack.ReadInt<int32>();
|
|
check(RESULT_PARAM);
|
|
FScriptArrayHelper ArrayHelper = FScriptArrayHelper::CreateHelperFormInnerProperty(InnerProperty, RESULT_PARAM);
|
|
ArrayHelper.EmptyValues(Num);
|
|
|
|
int32 i = 0;
|
|
while (*Stack.Code != EX_EndArrayConst)
|
|
{
|
|
ArrayHelper.AddValues(1);
|
|
Stack.Step(Stack.Object, ArrayHelper.GetRawPtr(i++));
|
|
}
|
|
ensure(i == Num);
|
|
|
|
P_FINISH; // EX_EndArrayConst
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ArrayConst, execArrayConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execSetConst)
|
|
{
|
|
FProperty* InnerProperty = CastFieldChecked<FProperty>((FField*)Stack.ReadObject());
|
|
int32 Num = Stack.ReadInt<int32>();
|
|
check(RESULT_PARAM);
|
|
|
|
FScriptSetHelper SetHelper = FScriptSetHelper::CreateHelperFormElementProperty(InnerProperty, RESULT_PARAM);
|
|
SetHelper.EmptyElements(Num);
|
|
|
|
while (*Stack.Code != EX_EndSetConst)
|
|
{
|
|
int32 Index = SetHelper.AddDefaultValue_Invalid_NeedsRehash();
|
|
Stack.Step(Stack.Object, SetHelper.GetElementPtr(Index));
|
|
}
|
|
SetHelper.Rehash();
|
|
|
|
P_FINISH; // EX_EndSetConst
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_SetConst, execSetConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execMapConst)
|
|
{
|
|
FProperty* KeyProperty = CastFieldChecked<FProperty>((FField*)Stack.ReadObject());
|
|
FProperty* ValProperty = CastFieldChecked<FProperty>((FField*)Stack.ReadObject());
|
|
int32 Num = Stack.ReadInt<int32>();
|
|
check(RESULT_PARAM);
|
|
|
|
FScriptMapHelper MapHelper = FScriptMapHelper::CreateHelperFormInnerProperties(KeyProperty, ValProperty, RESULT_PARAM);
|
|
MapHelper.EmptyValues(Num);
|
|
|
|
while (*Stack.Code != EX_EndMapConst)
|
|
{
|
|
int32 Index = MapHelper.AddDefaultValue_Invalid_NeedsRehash();
|
|
Stack.Step(Stack.Object, MapHelper.GetKeyPtr(Index));
|
|
Stack.Step(Stack.Object, MapHelper.GetValuePtr(Index));
|
|
}
|
|
MapHelper.Rehash();
|
|
|
|
P_FINISH; // EX_EndMapConst
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_MapConst, execMapConst);
|
|
|
|
DEFINE_FUNCTION(UObject::execIntZero)
|
|
{
|
|
*(int32*)RESULT_PARAM = 0;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_IntZero, execIntZero);
|
|
|
|
DEFINE_FUNCTION(UObject::execIntOne)
|
|
{
|
|
*(int32*)RESULT_PARAM = 1;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_IntOne, execIntOne);
|
|
|
|
DEFINE_FUNCTION(UObject::execTrue)
|
|
{
|
|
*(bool*)RESULT_PARAM = true;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_True, execTrue);
|
|
|
|
DEFINE_FUNCTION(UObject::execFalse)
|
|
{
|
|
*(bool*)RESULT_PARAM = false;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_False, execFalse);
|
|
|
|
DEFINE_FUNCTION(UObject::execNoObject)
|
|
{
|
|
*(UObject**)RESULT_PARAM = NULL;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_NoObject, execNoObject);
|
|
|
|
DEFINE_FUNCTION(UObject::execNullInterface)
|
|
{
|
|
FScriptInterface& InterfaceValue = *(FScriptInterface*)RESULT_PARAM;
|
|
InterfaceValue.SetObject(nullptr);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_NoInterface, execNullInterface);
|
|
|
|
DEFINE_FUNCTION(UObject::execIntConstByte)
|
|
{
|
|
*(int32*)RESULT_PARAM = *Stack.Code++;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_IntConstByte, execIntConstByte);
|
|
|
|
DEFINE_FUNCTION(UObject::execDynamicCast)
|
|
{
|
|
// Get "to cast to" class for the dynamic actor class
|
|
UClass* ClassPtr = (UClass*)Stack.ReadObject();
|
|
|
|
// Compile object expression.
|
|
UObject* Castee = NULL;
|
|
Stack.Step(Stack.Object, &Castee);
|
|
//*(UObject**)RESULT_PARAM = (Castee && Castee->IsA(Class)) ? Castee : NULL;
|
|
*(UObject**)RESULT_PARAM = NULL; // default value
|
|
|
|
if (ClassPtr)
|
|
{
|
|
// if we were passed in a null value
|
|
if (Castee == NULL)
|
|
{
|
|
if (ClassPtr->HasAnyClassFlags(CLASS_Interface))
|
|
{
|
|
((FScriptInterface*)RESULT_PARAM)->SetObject(NULL);
|
|
}
|
|
else
|
|
{
|
|
*(UObject**)RESULT_PARAM = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// check to see if the Castee is an implemented interface by looking up the
|
|
// class hierarchy and seeing if any class in said hierarchy implements the interface
|
|
if (ClassPtr->HasAnyClassFlags(CLASS_Interface))
|
|
{
|
|
if (Castee->GetClass()->ImplementsInterface(ClassPtr))
|
|
{
|
|
// interface property type - convert to FScriptInterface
|
|
((FScriptInterface*)RESULT_PARAM)->SetObject(Castee);
|
|
((FScriptInterface*)RESULT_PARAM)->SetInterface(Castee->GetInterfaceAddress(ClassPtr));
|
|
}
|
|
}
|
|
// check to see if the Castee is a castable class
|
|
else if (Castee->IsA(ClassPtr))
|
|
{
|
|
*(UObject**)RESULT_PARAM = Castee;
|
|
}
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_DynamicCast, execDynamicCast);
|
|
|
|
DEFINE_FUNCTION(UObject::execMetaCast)
|
|
{
|
|
UClass* MetaClass = (UClass*)Stack.ReadObject();
|
|
|
|
// Compile actor expression.
|
|
UObject* Castee = nullptr;
|
|
Stack.Step(Stack.Object, &Castee);
|
|
UClass* CasteeClass = dynamic_cast<UClass*>(Castee);
|
|
*(UObject**)RESULT_PARAM = (CasteeClass && CasteeClass->IsChildOf(MetaClass)) ? Castee : nullptr;
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_MetaCast, execMetaCast);
|
|
|
|
DEFINE_FUNCTION(UObject::execPrimitiveCast)
|
|
{
|
|
int32 B = *(Stack.Code)++;
|
|
(*GCasts[B])(Stack.Object, Stack, RESULT_PARAM);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_PrimitiveCast, execPrimitiveCast);
|
|
|
|
DEFINE_FUNCTION(UObject::execInterfaceCast)
|
|
{
|
|
(*GCasts[CST_ObjectToInterface])(Stack.Object, Stack, RESULT_PARAM);
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_ObjToInterfaceCast, execInterfaceCast);
|
|
|
|
DEFINE_FUNCTION(UObject::execObjectToBool)
|
|
{
|
|
UObject* Obj = NULL;
|
|
Stack.Step(Stack.Object, &Obj);
|
|
*(bool*)RESULT_PARAM = Obj != NULL;
|
|
}
|
|
IMPLEMENT_CAST_FUNCTION(CST_ObjectToBool, execObjectToBool);
|
|
|
|
DEFINE_FUNCTION(UObject::execInterfaceToBool)
|
|
{
|
|
FScriptInterface Interface;
|
|
Stack.Step(Stack.Object, &Interface);
|
|
*(bool*)RESULT_PARAM = (Interface.GetObject() != NULL);
|
|
}
|
|
IMPLEMENT_CAST_FUNCTION(CST_InterfaceToBool, execInterfaceToBool);
|
|
|
|
DEFINE_FUNCTION(UObject::execObjectToInterface)
|
|
{
|
|
FScriptInterface& InterfaceValue = *(FScriptInterface*)RESULT_PARAM;
|
|
|
|
// read the interface class off the stack
|
|
UClass* InterfaceClass = dynamic_cast<UClass*>(Stack.ReadObject());
|
|
checkSlow(InterfaceClass != NULL);
|
|
|
|
// read the object off the stack
|
|
UObject* ObjectValue = NULL;
|
|
Stack.Step(Stack.Object, &ObjectValue);
|
|
|
|
if (ObjectValue && ObjectValue->GetClass()->ImplementsInterface(InterfaceClass))
|
|
{
|
|
InterfaceValue.SetObject(ObjectValue);
|
|
|
|
void* IAddress = ObjectValue->GetInterfaceAddress(InterfaceClass);
|
|
InterfaceValue.SetInterface(IAddress);
|
|
}
|
|
else
|
|
{
|
|
InterfaceValue.SetObject(NULL);
|
|
}
|
|
}
|
|
IMPLEMENT_CAST_FUNCTION(CST_ObjectToInterface, execObjectToInterface);
|
|
|
|
DEFINE_FUNCTION(UObject::execInterfaceToInterface)
|
|
{
|
|
FScriptInterface& CastResult = *(FScriptInterface*)RESULT_PARAM;
|
|
|
|
// read the interface class off the stack
|
|
UClass* ClassToCastTo = dynamic_cast<UClass*>(Stack.ReadObject());
|
|
checkSlow(ClassToCastTo != NULL);
|
|
checkSlow(ClassToCastTo->HasAnyClassFlags(CLASS_Interface));
|
|
|
|
// read the input interface-object off the stack
|
|
FScriptInterface InterfaceInput;
|
|
Stack.Step(Stack.Object, &InterfaceInput);
|
|
|
|
UObject* ObjectWithInterface = InterfaceInput.GetObjectRef();
|
|
if ((ObjectWithInterface != NULL) && ObjectWithInterface->GetClass()->ImplementsInterface(ClassToCastTo))
|
|
{
|
|
CastResult.SetObject(ObjectWithInterface);
|
|
|
|
void* IAddress = ObjectWithInterface->GetInterfaceAddress(ClassToCastTo);
|
|
CastResult.SetInterface(IAddress);
|
|
}
|
|
else
|
|
{
|
|
CastResult.SetObject(NULL);
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_CrossInterfaceCast, execInterfaceToInterface);
|
|
|
|
DEFINE_FUNCTION(UObject::execInterfaceToObject)
|
|
{
|
|
// read the interface class off the stack
|
|
UClass* ObjClassToCastTo = dynamic_cast<UClass*>(Stack.ReadObject());
|
|
checkSlow(ObjClassToCastTo != nullptr);
|
|
|
|
// read the input interface-object off the stack
|
|
FScriptInterface InterfaceInput;
|
|
Stack.Step(Stack.Object, &InterfaceInput);
|
|
|
|
UObject* InputObjWithInterface = InterfaceInput.GetObjectRef();
|
|
if (InputObjWithInterface && InputObjWithInterface->IsA(ObjClassToCastTo))
|
|
{
|
|
*(UObject**)RESULT_PARAM = InputObjWithInterface;
|
|
}
|
|
else
|
|
{
|
|
*(UObject**)RESULT_PARAM = nullptr;
|
|
}
|
|
}
|
|
IMPLEMENT_VM_FUNCTION(EX_InterfaceToObjCast, execInterfaceToObject);
|
|
|
|
#undef LOCTEXT_NAMESPACE
|