Files
MingSimulator/Plugins/Puerts/Source/JsEnv/Private/JsEnvImpl.cpp
T
starmen1265 ed21658256 从master分支拉取puerts,并拉取v8制品,修改puerts插件部分UE5_5以下兼容的宏代码。保证puerts编译通过。包括
CSharpParamDefaultValueMetas.cs和ubtplugin.csproj和declarationGenerator.cpp和PropertyTranslator.cpp对UserDefinedStruct.h的引用
2024-12-02 01:03:52 +08:00

4814 lines
172 KiB
C++

/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#include "JsEnvImpl.h"
#include "JsEnvModule.h"
#include "DynamicDelegateProxy.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "StructWrapper.h"
#include "DelegateWrapper.h"
#include "ContainerWrapper.h"
#include "SoftObjectWrapper.h"
#include "V8Utils.h"
#include "ObjectMapper.h"
#include "JSLogger.h"
#if !defined(ENGINE_INDEPENDENT_JSENV)
#include "JSGeneratedClass.h"
#include "JSAnimGeneratedClass.h"
#include "JSWidgetGeneratedClass.h"
#include "JSGeneratedFunction.h"
#endif
#include "JSClassRegister.h"
#include "PromiseRejectCallback.hpp"
#if !defined(ENGINE_INDEPENDENT_JSENV)
#include "TypeScriptGeneratedClass.h"
#include "Engine/UserDefinedEnum.h"
#endif
#include "ContainerMeta.h"
#include "V8InspectorImpl.h"
#if USE_WASM3
#include "WasmModuleInstance.h"
#endif
#if !defined(WITH_NODEJS)
#if V8_MAJOR_VERSION < 8 && !defined(WITH_QUICKJS)
#if PLATFORM_WINDOWS
#include "Blob/Win64/NativesBlob.h"
#include "Blob/Win64/SnapshotBlob.h"
#elif PLATFORM_ANDROID_ARM
#include "Blob/Android/armv7a/NativesBlob.h"
#include "Blob/Android/armv7a/SnapshotBlob.h"
#elif PLATFORM_ANDROID_ARM64
#include "Blob/Android/arm64/NativesBlob.h"
#include "Blob/Android/arm64/SnapshotBlob.h"
#elif PLATFORM_MAC
#include "Blob/macOS/NativesBlob.h"
#include "Blob/macOS/SnapshotBlob.h"
#elif PLATFORM_IOS
#include "Blob/iOS/arm64/NativesBlob.h"
#include "Blob/iOS/arm64/SnapshotBlob.h"
#elif PLATFORM_LINUX
#include "Blob/Linux/NativesBlob.h"
#include "Blob/Linux/SnapshotBlob.h"
#endif
#endif
#else
PRAGMA_DISABLE_UNDEFINED_IDENTIFIER_WARNINGS
#if PLATFORM_WINDOWS
#include <windows.h>
#elif PLATFORM_LINUX
#include <sys/epoll.h>
#elif PLATFORM_MAC
#include <sys/select.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/types.h>
#endif
PRAGMA_ENABLE_UNDEFINED_IDENTIFIER_WARNINGS
#endif
#if !defined(ENGINE_INDEPENDENT_JSENV)
#include "Engine/CollisionProfile.h"
#endif
#if USE_WASM3
#include "PuertsWasm/WasmJsFunctionParams.h"
#endif
#if defined(WITH_WEBSOCKET)
void InitWebsocketPPWrap(v8::Local<v8::Context> Context);
#endif
namespace PUERTS_NAMESPACE
{
#if !defined(WITH_QUICKJS)
void LoadPesapiDll(const v8::FunctionCallbackInfo<v8::Value>& Info);
#endif
FJsEnvImpl::FJsEnvImpl(const FString& ScriptRoot)
: FJsEnvImpl(std::make_shared<DefaultJSModuleLoader>(ScriptRoot), std::make_shared<FDefaultLogger>(), -1, nullptr, FString(),
nullptr, nullptr)
{
}
static void FNameToArrayBuffer(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
FName Name = FV8Utils::ToFName(Info.GetIsolate(), Info[0]);
v8::Local<v8::ArrayBuffer> Ab = v8::ArrayBuffer::New(Info.GetIsolate(), sizeof(FName));
void* Buff = DataTransfer::GetArrayBufferData(Ab);
::memcpy(Buff, &Name, sizeof(FName));
Info.GetReturnValue().Set(Ab);
}
static void ToCString(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
auto Isolate = Info.GetIsolate();
if (!Info[0]->IsString())
{
FV8Utils::ThrowException(Isolate, "expect a string");
return;
}
v8::Local<v8::String> Str = Info[0]->ToString(Isolate->GetCurrentContext()).ToLocalChecked();
const size_t Length = Str->Utf8Length(Isolate);
v8::Local<v8::ArrayBuffer> Ab = v8::ArrayBuffer::New(Info.GetIsolate(), Length + 1);
char* Buff = static_cast<char*>(DataTransfer::GetArrayBufferData(Ab));
Str->WriteUtf8(Isolate, Buff);
Buff[Length] = '\0';
Info.GetReturnValue().Set(Ab);
}
static void ToCPtrArray(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
const size_t Length = sizeof(void*) * Info.Length();
v8::Local<v8::ArrayBuffer> Ret = v8::ArrayBuffer::New(Info.GetIsolate(), Length);
void** Buff = static_cast<void**>(DataTransfer::GetArrayBufferData(Ret));
for (int i = 0; i < Info.Length(); i++)
{
auto Val = Info[i];
void* Ptr = nullptr;
if (Val->IsArrayBufferView())
{
v8::Local<v8::ArrayBufferView> BuffView = Val.As<v8::ArrayBufferView>();
auto Ab = BuffView->Buffer();
Ptr = static_cast<char*>(DataTransfer::GetArrayBufferData(Ab)) + BuffView->ByteOffset();
}
else if (Val->IsArrayBuffer())
{
auto Ab = v8::Local<v8::ArrayBuffer>::Cast(Val);
Ptr = static_cast<char*>(DataTransfer::GetArrayBufferData(Ab));
}
Buff[i] = Ptr;
}
Info.GetReturnValue().Set(Ret);
}
#if defined(WITH_NODEJS)
void FJsEnvImpl::StartPolling()
{
uv_async_init(&NodeUVLoop, &DummyUVHandle, nullptr);
uv_sem_init(&PollingSem, 0);
uv_thread_create(
&PollingThread,
[](void* arg)
{
auto* self = static_cast<FJsEnvImpl*>(arg);
while (true)
{
uv_sem_wait(&self->PollingSem);
if (self->PollingClosed)
break;
self->PollEvents();
if (self->PollingClosed)
break;
self->LastJob = FFunctionGraphTask::CreateAndDispatchWhenReady(
[self]() { self->UvRunOnce(); }, TStatId{}, nullptr, ENamedThreads::GameThread);
}
},
this);
#if PLATFORM_WINDOWS
// on single-core the io comp port NumberOfConcurrentThreads needs to be 2
// to avoid cpu pegging likely caused by a busy loop in PollEvents
if (FPlatformMisc::NumberOfCores() == 1)
{
if (NodeUVLoop.iocp && NodeUVLoop.iocp != INVALID_HANDLE_VALUE)
CloseHandle(NodeUVLoop.iocp);
NodeUVLoop.iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 2);
}
#elif PLATFORM_LINUX
Epoll = epoll_create(1);
int backend_fd = uv_backend_fd(&NodeUVLoop);
struct epoll_event ev = {0};
ev.events = EPOLLIN;
ev.data.fd = backend_fd;
epoll_ctl(Epoll, EPOLL_CTL_ADD, backend_fd, &ev);
NodeUVLoop.data = this;
NodeUVLoop.on_watcher_queue_updated = OnWatcherQueueChanged;
#elif PLATFORM_MAC
NodeUVLoop.data = this;
NodeUVLoop.on_watcher_queue_updated = OnWatcherQueueChanged;
#endif
UvRunOnce();
}
void FJsEnvImpl::UvRunOnce()
{
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = v8::Local<v8::Context>::New(Isolate, DefaultContext);
v8::Context::Scope ContextScope(Context);
// TODO: catch uv_run可以让脚本错误不至于进程退出,但这不知道会不会对node有什么副作用
v8::TryCatch TryCatch(Isolate);
uv_run(&NodeUVLoop, UV_RUN_NOWAIT);
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("uv_run throw: %s"), *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
else
{
static_cast<node::MultiIsolatePlatform*>(IJsEnvModule::Get().GetV8Platform())->DrainTasks(Isolate);
}
LastJob = nullptr;
// Tell the Polling thread to continue.
uv_sem_post(&PollingSem);
}
void FJsEnvImpl::PollEvents()
{
#if PLATFORM_WINDOWS
DWORD bytes;
DWORD timeout = uv_backend_timeout(&NodeUVLoop);
ULONG_PTR key;
OVERLAPPED* overlapped;
timeout = timeout > 100 ? 100 : timeout;
GetQueuedCompletionStatus(NodeUVLoop.iocp, &bytes, &key, &overlapped, timeout);
// Give the event back so libuv can deal with it.
if (overlapped != NULL)
PostQueuedCompletionStatus(NodeUVLoop.iocp, bytes, key, overlapped);
#elif PLATFORM_LINUX
int timeout = uv_backend_timeout(&NodeUVLoop);
timeout = (timeout > 100 || timeout < 0) ? 100 : timeout;
// Wait for new libuv events.
int r;
do
{
struct epoll_event ev;
r = epoll_wait(Epoll, &ev, 1, timeout);
} while (r == -1 && errno == EINTR);
#elif PLATFORM_MAC
struct timeval tv;
int timeout = uv_backend_timeout(&NodeUVLoop);
timeout = (timeout > 100 || timeout < 0) ? 100 : timeout;
if (timeout != -1)
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
}
fd_set readset;
int fd = uv_backend_fd(&NodeUVLoop);
FD_ZERO(&readset);
FD_SET(fd, &readset);
// Wait for new libuv events.
int r;
do
{
r = select(fd + 1, &readset, nullptr, nullptr, timeout == -1 ? nullptr : &tv);
} while (r == -1 && errno == EINTR);
#endif
}
void FJsEnvImpl::OnWatcherQueueChanged(uv_loop_t* loop)
{
#if !PLATFORM_WINDOWS
FJsEnvImpl* self = static_cast<FJsEnvImpl*>(loop->data);
self->WakeupPollingThread();
#endif
}
void FJsEnvImpl::WakeupPollingThread()
{
uv_async_send(&DummyUVHandle);
}
void FJsEnvImpl::StopPolling()
{
PollingClosed = true;
uv_sem_post(&PollingSem);
WakeupPollingThread();
uv_thread_join(&PollingThread);
if (LastJob)
{
LastJob->Wait();
}
uv_sem_destroy(&PollingSem);
}
#endif
#if defined(WITH_V8_BYTECODE)
struct FCodeCacheHeader
{
uint32_t MagicNumber;
uint32_t VersionHash;
uint32_t SourceHash;
uint32_t FlagHash;
#if V8_MAJOR_VERSION >= 11
uint32_t ReadOnlySnapshotChecksum;
#endif
uint32_t PayloadLength;
uint32_t Checksum;
};
#endif
FJsEnvImpl::FJsEnvImpl(std::shared_ptr<IJSModuleLoader> InModuleLoader, std::shared_ptr<ILogger> InLogger, int InDebugPort,
std::function<void(const FString&)> InOnSourceLoadedCallback, const FString InFlags, void* InExternalRuntime,
void* InExternalContext)
{
GUObjectArray.AddUObjectDeleteListener(static_cast<FUObjectArray::FUObjectDeleteListener*>(this));
if (!InFlags.IsEmpty())
{
#if !defined(WITH_NODEJS) && !defined(WITH_QUICKJS)
TArray<FString> FlagArray;
InFlags.ParseIntoArray(FlagArray, TEXT(" "));
for (auto& Flag : FlagArray)
{
static FString Max_Old_Space_Size_Name(TEXT("--max-old-space-size="));
if (Flag.StartsWith(Max_Old_Space_Size_Name))
{
size_t Val = FCString::Atoi(*Flag.Mid(Max_Old_Space_Size_Name.Len()));
CreateParams.constraints.set_max_old_generation_size_in_bytes(Val * 1024 * 1024);
}
}
#endif
}
Started = false;
Inspector = nullptr;
InspectorChannel = nullptr;
ModuleLoader = std::move(InModuleLoader);
Logger = InLogger;
OnSourceLoadedCallback = InOnSourceLoadedCallback;
#if !defined(WITH_NODEJS)
#if V8_MAJOR_VERSION < 8 && !defined(WITH_QUICKJS)
std::unique_ptr<v8::StartupData> NativesBlob;
if (!NativesBlob)
{
NativesBlob = std::make_unique<v8::StartupData>();
NativesBlob->data = (const char*) NativesBlobCode;
NativesBlob->raw_size = sizeof(NativesBlobCode);
}
v8::V8::SetNativesDataBlob(NativesBlob.get());
#endif
CreateParams.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
#ifdef WITH_QUICKJS
MainIsolate = InExternalRuntime ? v8::Isolate::New(InExternalRuntime) : v8::Isolate::New(CreateParams);
#else
check(!InExternalRuntime && !InExternalContext);
MainIsolate = v8::Isolate::New(CreateParams);
#endif
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
Isolate->SetData(0, static_cast<IObjectMapper*>(this)); //直接传this会有问题,强转后地址会变
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
#ifdef WITH_QUICKJS
v8::Local<v8::Context> Context =
(InExternalRuntime && InExternalContext) ? v8::Context::New(Isolate, InExternalContext) : v8::Context::New(Isolate);
#else
v8::Local<v8::Context> Context = v8::Context::New(Isolate);
#endif
#else
int Argc = 1;
const char* Argv[] = {"puerts"};
std::vector<std::string> Args(Argv, Argv + Argc);
std::vector<std::string> ExecArgs;
std::vector<std::string> Errors;
const int Ret = uv_loop_init(&NodeUVLoop);
if (Ret != 0)
{
Logger->Error(FString::Printf(TEXT("Failed to initialize loop: %s\n"), UTF8_TO_TCHAR(uv_err_name(Ret))));
return;
}
CreateParams.array_buffer_allocator = nullptr;
NodeArrayBufferAllocator = node::ArrayBufferAllocator::Create();
auto Platform = static_cast<node::MultiIsolatePlatform*>(IJsEnvModule::Get().GetV8Platform());
MainIsolate = node::NewIsolate(NodeArrayBufferAllocator.get(), &NodeUVLoop, Platform);
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
Isolate->SetData(0, static_cast<IObjectMapper*>(this)); //直接传this会有问题,强转后地址会变
// v8::Locker locker(Isolate);
// difference from embedding example, if lock, blow check fail:
// Utils::ApiCheck(
//! v8::Locker::IsActive() ||
// internal_isolate->thread_manager()->IsLockedByCurrentThread() ||
// internal_isolate->serializer_enabled(),
//"HandleScope::HandleScope",
//"Entering the V8 API without proper locking in place");
v8::Isolate::Scope Isolatescope(Isolate);
NodeIsolateData =
node::CreateIsolateData(Isolate, &NodeUVLoop, Platform, NodeArrayBufferAllocator.get()); // node::FreeIsolateData
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = node::NewContext(Isolate);
#endif
DefaultContext.Reset(Isolate, Context);
v8::Context::Scope ContextScope(Context);
#if defined(WITH_NODEJS)
// kDefaultFlags = kOwnsProcessState | kOwnsInspector, if kOwnsInspector set, inspector_agent.cc:681
// CHECK_EQ(start_io_thread_async_initialized.exchange(true), false) fail!
NodeEnv = CreateEnvironment(NodeIsolateData, Context, Args, ExecArgs, node::EnvironmentFlags::kOwnsProcessState);
v8::MaybeLocal<v8::Value> LoadenvRet = node::LoadEnvironment(NodeEnv,
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;");
if (LoadenvRet.IsEmpty()) // There has been a JS exception.
{
return;
}
// the same as raw v8
Isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
StartPolling();
#endif
v8::Local<v8::Object> Global = Context->Global();
v8::Local<v8::Object> PuertsObj = v8::Object::New(Isolate);
Global->Set(Context, FV8Utils::InternalString(Isolate, "puerts"), PuertsObj).Check();
auto This = v8::External::New(Isolate, this);
MethodBindingHelper<&FJsEnvImpl::EvalScript>::Bind(Isolate, Context, Global, "__tgjsEvalScript", This);
MethodBindingHelper<&FJsEnvImpl::Log>::Bind(Isolate, Context, Global, "__tgjsLog", This);
MethodBindingHelper<&FJsEnvImpl::SearchModule>::Bind(Isolate, Context, Global, "__tgjsSearchModule", This);
MethodBindingHelper<&FJsEnvImpl::LoadModule>::Bind(Isolate, Context, Global, "__tgjsLoadModule", This);
MethodBindingHelper<&FJsEnvImpl::LoadUEType>::Bind(Isolate, Context, PuertsObj, "loadUEType", This);
MethodBindingHelper<&FJsEnvImpl::LoadCppType>::Bind(Isolate, Context, PuertsObj, "loadCPPType", This);
MethodBindingHelper<&FJsEnvImpl::UEClassToJSClass>::Bind(Isolate, Context, Global, "__tgjsUEClassToJSClass", This);
MethodBindingHelper<&FJsEnvImpl::NewContainer>::Bind(Isolate, Context, Global, "__tgjsNewContainer", This);
MethodBindingHelper<&FJsEnvImpl::MergeObject>::Bind(Isolate, Context, Global, "__tgjsMergeObject", This);
MethodBindingHelper<&FJsEnvImpl::NewObjectByClass>::Bind(Isolate, Context, Global, "__tgjsNewObject", This);
MethodBindingHelper<&FJsEnvImpl::SetJsTakeRefInTs>::Bind(Isolate, Context, Global, "__tgjsSetJsTakeRef", This);
MethodBindingHelper<&FJsEnvImpl::NewStructByScriptStruct>::Bind(Isolate, Context, Global, "__tgjsNewStruct", This);
#if !defined(ENGINE_INDEPENDENT_JSENV)
MethodBindingHelper<&FJsEnvImpl::MakeUClass>::Bind(Isolate, Context, Global, "__tgjsMakeUClass", This);
MethodBindingHelper<&FJsEnvImpl::Mixin>::Bind(Isolate, Context, Global, "__tgjsMixin", This);
#endif
MethodBindingHelper<&FJsEnvImpl::FindModule>::Bind(Isolate, Context, Global, "__tgjsFindModule", This);
MethodBindingHelper<&FJsEnvImpl::SetInspectorCallback>::Bind(Isolate, Context, Global, "__tgjsSetInspectorCallback", This);
MethodBindingHelper<&FJsEnvImpl::DispatchProtocolMessage>::Bind(
Isolate, Context, Global, "__tgjsDispatchProtocolMessage", This);
Isolate->SetPromiseRejectCallback(&PromiseRejectCallback<FJsEnvImpl>);
Global
->Set(Context, FV8Utils::ToV8String(Isolate, "__tgjsSetPromiseRejectCallback"),
v8::FunctionTemplate::New(Isolate, &SetPromiseRejectCallback<FJsEnvImpl>)->GetFunction(Context).ToLocalChecked())
.Check();
//#if !defined(WITH_NODEJS)
MethodBindingHelper<&FJsEnvImpl::SetTimeout>::Bind(Isolate, Context, Global, "setTimeout", This);
MethodBindingHelper<&FJsEnvImpl::ClearInterval>::Bind(Isolate, Context, Global, "clearTimeout", This);
MethodBindingHelper<&FJsEnvImpl::SetInterval>::Bind(Isolate, Context, Global, "setInterval", This);
MethodBindingHelper<&FJsEnvImpl::ClearInterval>::Bind(Isolate, Context, Global, "clearInterval", This);
//#endif
#if USE_WASM3
MethodBindingHelper<&FJsEnvImpl::Wasm_NewMemory>::Bind(Isolate, Context, Global, "__tgjsWasm_NewMemory", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_MemoryGrowth>::Bind(Isolate, Context, Global, "__tgjsWasm_MemoryGrowth", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_MemoryBuffer>::Bind(Isolate, Context, Global, "__tgjsWasm_MemoryBuffer", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_TableGrowth>::Bind(Isolate, Context, Global, "__tgjsWasm_TableGrow", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_TableSet>::Bind(Isolate, Context, Global, "__tgjsWasm_TableSet", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_TableLen>::Bind(Isolate, Context, Global, "__tgjsWasm_TableLen", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_Instance>::Bind(Isolate, Context, Global, "__tgjsWasm_Instance", This);
MethodBindingHelper<&FJsEnvImpl::Wasm_OverrideWebAssembly>::Bind(
Isolate, Context, Global, "__tgjsWasm_OverrideWebAssembly", This);
#endif
MethodBindingHelper<&FJsEnvImpl::DumpStatisticsLog>::Bind(Isolate, Context, Global, "dumpStatisticsLog", This);
Global
->Set(Context, FV8Utils::ToV8String(Isolate, "__tgjsFNameToArrayBuffer"),
v8::FunctionTemplate::New(Isolate, FNameToArrayBuffer)->GetFunction(Context).ToLocalChecked())
.Check();
PuertsObj
->Set(Context, FV8Utils::ToV8String(Isolate, "toCString"),
v8::FunctionTemplate::New(Isolate, ToCString)->GetFunction(Context).ToLocalChecked())
.Check();
PuertsObj
->Set(Context, FV8Utils::ToV8String(Isolate, "toCPtrArray"),
v8::FunctionTemplate::New(Isolate, ToCPtrArray)->GetFunction(Context).ToLocalChecked())
.Check();
#if !defined(WITH_QUICKJS)
PuertsObj
->Set(Context, FV8Utils::ToV8String(Isolate, "load"),
v8::FunctionTemplate::New(Isolate, LoadPesapiDll)->GetFunction(Context).ToLocalChecked())
.Check();
#endif
FString DllExt =
#if PLATFORM_WINDOWS
TEXT(".dll");
#elif PLATFORM_MAC || PLATFORM_IOS
TEXT(".dylib");
#else
TEXT(".so");
#endif
PuertsObj->Set(Context, FV8Utils::ToV8String(Isolate, "dll_ext"), FV8Utils::ToV8String(Isolate, DllExt)).Check();
MethodBindingHelper<&FJsEnvImpl::ReleaseManualReleaseDelegate>::Bind(
Isolate, Context, PuertsObj, "releaseManualReleaseDelegate", This);
ArrayTemplate = v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FScriptArrayWrapper::ToFunctionTemplate(Isolate));
SetTemplate = v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FScriptSetWrapper::ToFunctionTemplate(Isolate));
MapTemplate = v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FScriptMapWrapper::ToFunctionTemplate(Isolate));
FixSizeArrayTemplate = v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FFixSizeArrayWrapper::ToFunctionTemplate(Isolate));
CppObjectMapper.Initialize(Isolate, Context);
DelegateTemplate = v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FDelegateWrapper::ToFunctionTemplate(Isolate));
MulticastDelegateTemplate =
v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FMulticastDelegateWrapper::ToFunctionTemplate(Isolate));
SoftObjectPtrTemplate = v8::UniquePersistent<v8::FunctionTemplate>(Isolate, FSoftObjectWrapper::ToFunctionTemplate(Isolate));
DynamicInvoker = MakeShared<DynamicInvokerImpl, ESPMode::ThreadSafe>(this);
MixinInvoker = DynamicInvoker;
#if !defined(ENGINE_INDEPENDENT_JSENV)
TsDynamicInvoker = MakeShared<TsDynamicInvokerImpl, ESPMode::ThreadSafe>(this);
#endif
Inspector = CreateV8Inspector(InDebugPort, &Context);
ExecuteModule("puerts/first_run.js");
#if !defined(WITH_NODEJS)
ExecuteModule("puerts/polyfill.js");
#endif
ExecuteModule("puerts/log.js");
ExecuteModule("puerts/modular.js");
ExecuteModule("puerts/uelazyload.js");
ExecuteModule("puerts/events.js");
ExecuteModule("puerts/promises.js");
ExecuteModule("puerts/argv.js");
ExecuteModule("puerts/jit_stub.js");
ExecuteModule("puerts/hot_reload.js");
ExecuteModule("puerts/pesaddon.js");
Require.Reset(Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "__require")).ToLocalChecked().As<v8::Function>());
GetESMMain.Reset(
Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "getESMMain")).ToLocalChecked().As<v8::Function>());
ReloadJs.Reset(Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "__reload")).ToLocalChecked().As<v8::Function>());
#if !PUERTS_FORCE_CPP_UFUNCTION
MergePrototype.Reset(
Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "__mergePrototype")).ToLocalChecked().As<v8::Function>());
#endif
RemoveListItem.Reset(
Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "__removeListItem")).ToLocalChecked().As<v8::Function>());
GenListApply.Reset(
Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "__genListApply")).ToLocalChecked().As<v8::Function>());
#if defined(WITH_V8_BYTECODE)
GenEmptyCode.Reset(
Isolate, PuertsObj->Get(Context, FV8Utils::ToV8String(Isolate, "generateEmptyCode")).ToLocalChecked().As<v8::Function>());
#endif
DelegateProxiesCheckerHandler =
FUETicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FJsEnvImpl::CheckDelegateProxies), 1);
ManualReleaseCallbackMap.Reset(Isolate, v8::Map::New(Isolate));
UserObjectRetainer.SetName(TEXT("Puerts_UserObjectRetainer"));
SysObjectRetainer.SetName(TEXT("Puerts_SysObjectRetainer"));
#ifdef SINGLE_THREAD_VERIFY
BoundThreadId = FPlatformTLS::GetCurrentThreadId();
#endif
#if USE_WASM3
PuertsWasmEnv = std::make_shared<WasmEnv>();
//创建默认的runtime
PuertsWasmRuntimeList.Add(std::make_shared<WasmRuntime>(PuertsWasmEnv.get()));
ExecuteModule("puerts/wasm3_helper.js");
#endif
#if defined(WITH_V8_BYTECODE)
auto Script = v8::Script::Compile(Context, FV8Utils::ToV8String(Isolate, "")).ToLocalChecked();
auto CachedCode = v8::ScriptCompiler::CreateCodeCache(Script->GetUnboundScript());
const FCodeCacheHeader* CodeCacheHeader = (const FCodeCacheHeader*) CachedCode->data;
Expect_FlagHash = CodeCacheHeader->FlagHash; // get FlagHash
#if V8_MAJOR_VERSION >= 11
Expect_ReadOnlySnapshotChecksum = CodeCacheHeader->ReadOnlySnapshotChecksum;
#endif
#if !WITH_EDITOR
delete CachedCode; //编辑器下是v8.dll分配的,ue里的delete被重载了,这delete会有问题
#endif
#endif
#if defined(WITH_WEBSOCKET)
InitWebsocketPPWrap(Context);
ExecuteModule("puerts/websocketpp.js");
#endif
}
// #lizard forgives
FJsEnvImpl::~FJsEnvImpl()
{
#if USE_WASM3
PuertsWasmRuntimeList.Empty();
PuertsWasmEnv.reset();
for (auto Item : PuertsWasmCachedLinkFunctionList)
{
Item->CachedFunction.Reset();
delete Item;
}
PuertsWasmCachedLinkFunctionList.Empty();
#endif
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#if defined(WITH_NODEJS)
StopPolling();
#endif
#ifndef WITH_QUICKJS
for (auto& KV : HashToModuleInfo)
{
delete KV.second;
}
HashToModuleInfo.clear();
PathToModule.Empty();
#endif
for (int i = 0; i < ManualReleaseCallbackList.size(); i++)
{
if (ManualReleaseCallbackList[i].IsValid(true))
{
ManualReleaseCallbackList[i].Get()->JsFunction.Reset();
}
}
ManualReleaseCallbackMap.Reset();
InspectorMessageHandler.Reset();
Require.Reset();
GetESMMain.Reset();
ReloadJs.Reset();
JsPromiseRejectCallback.Reset();
FUETicker::GetCoreTicker().RemoveTicker(DelegateProxiesCheckerHandler);
{
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
TypeToTemplateInfoMap.Empty();
CppObjectMapper.UnInitialize(Isolate);
ObjectMap.Empty();
for (auto& KV : StructCache)
{
FObjectCacheNode* PNode = &KV.Value;
while (PNode)
{
PNode->Value.Reset();
PNode = PNode->Next;
}
}
for (auto& KV : ContainerCache)
{
if (KV.Value.NeedRelease)
{
switch (KV.Value.Type)
{
case EArray:
delete static_cast<FScriptArrayEx*>(KV.Key);
break;
case EMap:
delete static_cast<FScriptMapEx*>(KV.Key);
break;
case ESet:
delete static_cast<FScriptSetEx*>(KV.Key);
break;
}
}
}
ContainerCache.Empty();
for (auto Iter = DelegateMap.begin(); Iter != DelegateMap.end(); Iter++)
{
Iter->second.JSObject.Reset();
if (Iter->second.Proxy.IsValid(true))
{
Iter->second.Proxy->JsFunction.Reset();
}
if (!Iter->second.PassByPointer)
{
delete ((FScriptDelegate*) Iter->first);
}
Iter->second.JsCallbacks.Reset();
}
for (auto& KV : AutoReleaseCallbacksMap)
{
for (auto& Callback : KV.Value)
{
if (Callback.IsValid(true))
{
Callback->JsFunction.Reset();
}
}
}
TsFunctionMap.Empty();
MixinFunctionMap.Empty();
#if !defined(ENGINE_INDEPENDENT_JSENV)
TsDynamicInvoker.Reset();
BindInfoMap.Empty();
#endif
for (auto Iter = TimerInfos.CreateIterator(); Iter; ++Iter)
{
Iter->Value.Callback.Reset();
FUETicker::GetCoreTicker().RemoveTicker(Iter->Value.TickerHandle);
}
TimerInfos.Empty();
#if !defined(ENGINE_INDEPENDENT_JSENV)
for (auto& GeneratedClass : GeneratedClasses)
{
if (auto JSGeneratedClass = Cast<UJSGeneratedClass>(GeneratedClass))
{
if (JSGeneratedClass->IsValidLowLevelFast() && !UEObjectIsPendingKill(JSGeneratedClass))
{
JSGeneratedClass->Release();
}
}
else if (auto JSWidgetGeneratedClass = Cast<UJSWidgetGeneratedClass>(GeneratedClass))
{
if (JSWidgetGeneratedClass->IsValidLowLevelFast() && !UEObjectIsPendingKill(JSWidgetGeneratedClass))
{
JSWidgetGeneratedClass->Release();
}
}
else if (auto JSAnimGeneratedClass = Cast<UJSAnimGeneratedClass>(GeneratedClass))
{
if (JSWidgetGeneratedClass->IsValidLowLevelFast() && !UEObjectIsPendingKill(JSWidgetGeneratedClass))
{
JSAnimGeneratedClass->Release();
}
}
}
#endif
#if defined(WITH_NODEJS)
node::EmitExit(NodeEnv);
node::Stop(NodeEnv);
node::FreeEnvironment(NodeEnv);
node::FreeIsolateData(NodeIsolateData);
auto Platform = static_cast<node::MultiIsolatePlatform*>(IJsEnvModule::Get().GetV8Platform());
Platform->UnregisterIsolate(Isolate);
#endif
if (InspectorChannel)
{
delete InspectorChannel;
InspectorChannel = nullptr;
}
if (Inspector)
{
delete Inspector;
Inspector = nullptr;
}
DynamicInvoker.Reset();
MixinInvoker.Reset();
SoftObjectPtrTemplate.Reset();
MulticastDelegateTemplate.Reset();
DelegateTemplate.Reset();
FixSizeArrayTemplate.Reset();
MapTemplate.Reset();
SetTemplate.Reset();
ArrayTemplate.Reset();
#if !PUERTS_FORCE_CPP_UFUNCTION
MergePrototype.Reset();
#endif
RemoveListItem.Reset();
GenListApply.Reset();
#if defined(WITH_V8_BYTECODE)
GenEmptyCode.Reset();
#endif
}
#if !defined(ENGINE_INDEPENDENT_JSENV)
for (size_t i = 0; i < MixinClasses.Num(); i++)
{
if (MixinClasses[i].IsValid())
{
UJSGeneratedClass::Restore(MixinClasses[i].Get());
}
}
#endif
#if PUERTS_REUSE_STRUCTWRAPPER_FUNCTIONTEMPLATE
for (auto Iter = TypeReflectionMap.CreateIterator(); Iter; ++Iter)
{
Iter->Value->CachedFunctionTemplate.Reset();
}
#endif
DefaultContext.Reset();
MainIsolate->Dispose();
MainIsolate = nullptr;
delete CreateParams.array_buffer_allocator;
GUObjectArray.RemoveUObjectDeleteListener(static_cast<FUObjectArray::FUObjectDeleteListener*>(this));
// quickjs will call UnBind in vm dispose, so cleanup move to here
for (auto& KV : StructCache)
{
FObjectCacheNode* PNode = &KV.Value;
while (PNode)
{
if (PNode->UserData)
{
FScriptStructWrapper* ScriptStructWrapper = (FScriptStructWrapper*) (PNode->UserData);
ScriptStructWrapper->Free(KV.Key);
}
PNode = PNode->Next;
}
}
StructCache.Empty();
}
void FJsEnvImpl::InitExtensionMethodsMap()
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
#if !defined(ENGINE_INDEPENDENT_JSENV)
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* Class = *It;
if (Class->IsChildOf<UExtensionMethods>() && Class->IsNative())
{
for (TFieldIterator<UFunction> FuncIt(Class, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
{
UFunction* Function = *FuncIt;
if (Function->HasAnyFunctionFlags(FUNC_Static))
{
TFieldIterator<PropertyMacro> ParamIt(Function);
if (ParamIt &&
((ParamIt->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm)) // has at least one param
{
UStruct* Struct = nullptr;
if (auto ObjectPropertyBase = CastFieldMacro<ObjectPropertyBaseMacro>(*ParamIt))
{
Struct = ObjectPropertyBase->PropertyClass;
}
else if (auto StructProperty = CastFieldMacro<StructPropertyMacro>(*ParamIt))
{
Struct = StructProperty->Struct;
}
if (Struct)
{
if (ExtensionMethodsMap.find(Struct) == ExtensionMethodsMap.end())
{
ExtensionMethodsMap[Struct] = std::vector<UFunction*>();
}
auto Iter = ExtensionMethodsMap.find(Struct);
if (std::find(Iter->second.begin(), Iter->second.end(), Function) == Iter->second.end())
{
Iter->second.push_back(Function);
}
}
}
}
}
}
}
#endif
ExtensionMethodsMapInited = true;
}
std::unique_ptr<FJsEnvImpl::ObjectMerger>& FJsEnvImpl::GetObjectMerger(UStruct* Struct)
{
if (ObjectMergers.find(Struct) == ObjectMergers.end())
{
ObjectMergers[Struct] = std::make_unique<ObjectMerger>(this, Struct);
}
return ObjectMergers[Struct];
}
void FJsEnvImpl::Merge(v8::Isolate* Isolate, v8::Local<v8::Context> Context, v8::Local<v8::Object> Src, UStruct* DesType, void* Des)
{
GetObjectMerger(DesType)->Merge(Isolate, Context, Src, Des);
}
void FJsEnvImpl::MergeObject(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgObject, EArgObject);
auto Des = Info[0]->ToObject(Context).ToLocalChecked();
auto Src = Info[1]->ToObject(Context).ToLocalChecked();
if (FV8Utils::GetPointerFast<void>(Des, 1)) // struct
{
if (auto Object = FV8Utils::GetUObject(Des, 1))
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
auto Struct = Cast<UScriptStruct>(Object);
if (Struct)
{
Merge(Isolate, Context, Src, Struct, FV8Utils::GetPointer(Des));
return;
}
}
}
else // class
{
auto Object = FV8Utils::GetUObject(Des);
if (Object)
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
Merge(Isolate, Context, Src, Object->GetClass(), Object);
return;
}
}
FV8Utils::ThrowException(Isolate, "Bad parameters #1, expect a native object.");
}
void FJsEnvImpl::NewObjectByClass(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
UObject* Outer = GetTransientPackage();
FName Name = NAME_None;
EObjectFlags ObjectFlags = RF_NoFlags;
UClass* Class = nullptr;
if (UObject* Object = FV8Utils::GetUObject(Context, Info[0]))
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
Class = Cast<UClass>(Object);
}
if (Class)
{
if (Info.Length() > 1 && !Info[1]->IsNullOrUndefined())
{
Outer = FV8Utils::GetUObject(Context, Info[1]);
if (FV8Utils::IsReleasedPtr(Outer))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
}
if (Info.Length() > 2 && !Info[2]->IsNullOrUndefined())
{
Name = FName(*FV8Utils::ToFString(Isolate, Info[2]));
}
if (Info.Length() > 3 && !Info[3]->IsNullOrUndefined())
{
ObjectFlags = (EObjectFlags) (Info[3]->Int32Value(Context).ToChecked());
}
UObject* Object = NewObject<UObject>(Outer, Class, Name, ObjectFlags);
auto Result = FV8Utils::IsolateData<IObjectMapper>(Isolate)->FindOrAdd(Isolate, Context, Object->GetClass(), Object);
#if !PUERTS_KEEP_UOBJECT_REFERENCE
bool NeedJsTakeRef = true;
if (Info.Length() > 4 && !Info[4]->IsNullOrUndefined())
{
if (!Info[4]->BooleanValue(Isolate))
{
NeedJsTakeRef = false;
}
}
if (NeedJsTakeRef)
{
bool Existed;
auto TemplateInfoPtr = GetTemplateInfoOfType(Class, Existed);
SetJsTakeRef(Object, static_cast<FClassWrapper*>(TemplateInfoPtr->StructWrapper.get()));
}
#endif
Info.GetReturnValue().Set(Result);
}
else
{
FV8Utils::ThrowException(Isolate, "invalid argument");
}
}
void FJsEnvImpl::NewStructByScriptStruct(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
UScriptStruct* ScriptStruct = nullptr;
if (UObject* Object = FV8Utils::GetUObject(Context, Info[0]))
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
ScriptStruct = Cast<UScriptStruct>(Object);
}
if (ScriptStruct)
{
void* Ptr = FScriptStructWrapper::Alloc(ScriptStruct);
Info.GetReturnValue().Set(
FV8Utils::IsolateData<IObjectMapper>(Isolate)->FindOrAddStruct(Isolate, Context, ScriptStruct, Ptr, false));
}
else
{
FV8Utils::ThrowException(Isolate, "invalid argument");
}
}
bool FJsEnvImpl::IdleNotificationDeadline(double DeadlineInSeconds)
{
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
#ifndef WITH_QUICKJS
return MainIsolate->IdleNotificationDeadline(DeadlineInSeconds);
#else
return true;
#endif
}
void FJsEnvImpl::LowMemoryNotification()
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
MainIsolate->LowMemoryNotification();
}
void FJsEnvImpl::RequestMinorGarbageCollectionForTesting()
{
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
#ifndef WITH_QUICKJS
MainIsolate->RequestGarbageCollectionForTesting(v8::Isolate::kMinorGarbageCollection);
#endif
}
void FJsEnvImpl::RequestFullGarbageCollectionForTesting()
{
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
#ifndef WITH_QUICKJS
MainIsolate->RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection);
#endif
}
#if !defined(ENGINE_INDEPENDENT_JSENV)
void FJsEnvImpl::FinishInjection(UClass* InClass)
{
while (InClass && !InClass->IsNative())
{
auto TempTypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(InClass);
if (TempTypeScriptGeneratedClass) // InjectNotFinished状态下,其子类的CDO对象构建,把UFunction设置为Native
{
auto BindInfoPtr = BindInfoMap.Find(TempTypeScriptGeneratedClass);
if (BindInfoPtr && BindInfoPtr->InjectNotFinished)
{
TempTypeScriptGeneratedClass->RedirectToTypeScriptFinish();
BindInfoPtr->InjectNotFinished = false;
}
}
InClass = InClass->GetSuperClass();
}
}
void FJsEnvImpl::MakeSureInject(UTypeScriptGeneratedClass* TypeScriptGeneratedClass, bool ForceReinject, bool RebindObject)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#if WITH_EDITOR
if (TypeScriptGeneratedClass->NeedReBind)
{
UTypeScriptGeneratedClass::NotifyRebind(TypeScriptGeneratedClass);
}
#endif
auto BindInfoPtr = BindInfoMap.Find(TypeScriptGeneratedClass);
if (!BindInfoPtr || ForceReinject) // create and link
{
auto Package = Cast<UPackage>(TypeScriptGeneratedClass->GetOuter());
if (!Package || TypeScriptGeneratedClass->NotSupportInject())
{
return;
}
auto PackageName = Package->GetName();
static FString PackageNamePrefix(TEXT(TS_BLUEPRINT_PATH));
auto PrefixPos = PackageName.Find(PackageNamePrefix);
if (PrefixPos != INDEX_NONE)
{
auto SuperClass = Cast<UTypeScriptGeneratedClass>(TypeScriptGeneratedClass->GetSuperClass());
if (SuperClass && SuperClass->GetName().StartsWith(TEXT("REINST_")))
{
//中间状态,父类修改,子类可能有个中间状态,其父类可能被修改为一个REINST_前缀的临时类
return;
}
if (SuperClass)
{
MakeSureInject(SuperClass, false, RebindObject);
}
FString ModuleName = PackageName.Mid(PrefixPos + PackageNamePrefix.Len());
Logger->Info(FString::Printf(TEXT("Bind module [%s] "), *ModuleName));
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
auto LocalRequire = Require.Get(Isolate);
if (!BindInfoPtr)
{
FBindInfo BindInfo;
BindInfo.Name = *ModuleName;
BindInfo.Prototype.Reset(Isolate, v8::Object::New(Isolate));
BindInfo.InjectNotFinished = true;
BindInfoMap.Emplace(TypeScriptGeneratedClass, std::move(BindInfo));
}
v8::TryCatch TryCatch(Isolate);
v8::Local<v8::Value> Args[] = {FV8Utils::ToV8String(Isolate, ModuleName)};
auto MaybeRet = LocalRequire->Call(Context, v8::Undefined(Isolate), 1, Args);
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(
TEXT("load module [%s] exception %s"), *ModuleName, *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
return;
}
if (!MaybeRet.IsEmpty())
{
auto Ret = MaybeRet.ToLocalChecked().As<v8::Object>();
auto MaybeFunc = Ret->Get(Context, FV8Utils::ToV8String(Isolate, "default"));
v8::Local<v8::Value> Val;
if (MaybeFunc.ToLocal(&Val) && Val->IsFunction())
{
auto Func = Val.As<v8::Function>();
v8::Local<v8::Value> VProto;
// UE_LOG(LogTemp, Error, TEXT("found function for , %s"), *ModuleName);
if (Func->Get(Context, FV8Utils::ToV8String(Isolate, "prototype")).ToLocal(&VProto) && VProto->IsObject())
{
// UE_LOG(LogTemp, Error, TEXT("found proto for , %s"), *ModuleName);
v8::Local<v8::Object> Proto = VProto.As<v8::Object>();
TypeScriptGeneratedClass->DynamicInvoker = TsDynamicInvoker;
// BindInfo.Prototype.Reset(Isolate, Proto);
TypeScriptGeneratedClass->ClassConstructor = &UTypeScriptGeneratedClass::StaticConstructor;
// BindInfo.Rebind = false;
v8::Local<v8::Value> VCtor;
if (Proto->Get(Context, FV8Utils::ToV8String(Isolate, "Constructor")).ToLocal(&VCtor) &&
VCtor->IsFunction())
{
// UE_LOG(LogTemp, Error, TEXT("found ctor for , %s"), *ModuleName);
BindInfoMap[TypeScriptGeneratedClass].Constructor.Reset(Isolate, VCtor.As<v8::Function>());
// BindInfo.Prototype.Reset(Isolate, Proto);
}
// SysObjectRetainer.Retain(Class);
// implement by js
TypeScriptGeneratedClass->FunctionToRedirect.Empty();
#if !PUERTS_FORCE_CPP_UFUNCTION
auto NetMethods = v8::Object::New(Isolate);
#endif
for (TFieldIterator<UFunction> It(TypeScriptGeneratedClass, EFieldIteratorFlags::ExcludeSuper,
EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::ExcludeInterfaces);
It; ++It)
{
UFunction* Function = *It;
auto FunctionFName = Function->GetFName();
FString FunctionName = Function->GetName();
// FString::Printf(TEXT("InpAxisEvt_%s_%s"), *InputAxisName.ToString(), *GetName())
static FString AxisPrefix(TEXT("InpAxisEvt_"));
if (FunctionName.StartsWith(AxisPrefix))
{
auto FunctionNameWithoutPrefix = FunctionName.Mid(AxisPrefix.Len());
int32 SubPos;
if (FunctionNameWithoutPrefix.FindChar('_', SubPos))
{
FunctionName = FunctionNameWithoutPrefix.Mid(0, SubPos);
}
}
static FString ActionPrefix(TEXT("InpActEvt_"));
if (FunctionName.StartsWith(ActionPrefix))
{
auto FunctionNameWithoutPrefix = FunctionName.Mid(ActionPrefix.Len());
int32 SubPos;
if (FunctionNameWithoutPrefix.FindChar('_', SubPos))
{
FunctionName = FunctionNameWithoutPrefix.Mid(0, SubPos);
}
}
auto V8Name = FV8Utils::ToV8String(Isolate, FunctionName);
v8::Local<v8::Object> FuncsObj =
Function->HasAnyFunctionFlags(FUNC_Static) ? static_cast<v8::Local<v8::Object>>(Func) : Proto;
if (!TypeScriptGeneratedClass->FunctionToRedirect.Contains(FunctionFName) &&
FuncsObj->HasOwnProperty(Context, V8Name).ToChecked() &&
(Function->HasAnyFunctionFlags(FUNC_BlueprintEvent)))
{
auto MaybeValue = FuncsObj->Get(Context, V8Name);
if (!MaybeValue.IsEmpty() && MaybeValue.ToLocalChecked()->IsFunction())
{
// Logger->Warn(FString::Printf(TEXT("override: %s:%s"), *TypeScriptGeneratedClass->GetName(),
// *Function->GetName())); UJSGeneratedClass::Override(Isolate, TypeScriptGeneratedClass,
// Function, v8::Local<v8::Function>::Cast(MaybeValue.ToLocalChecked()), DynamicInvoker, false);
auto FuncInfo = TsFunctionMap.Find(Function);
if (!FuncInfo)
{
TsFunctionMap.Add(
Function, {v8::UniquePersistent<v8::Function>(
Isolate, v8::Local<v8::Function>::Cast(MaybeValue.ToLocalChecked())),
std::make_unique<FFunctionTranslator>(Function, false)});
}
else
{
FuncInfo->FunctionTranslator->Init(Function, false);
FuncInfo->JsFunction = v8::UniquePersistent<v8::Function>(
Isolate, v8::Local<v8::Function>::Cast(MaybeValue.ToLocalChecked()));
}
#if !PUERTS_FORCE_CPP_UFUNCTION
if (Function->HasAnyFunctionFlags(FUNC_Net))
{
__USE(NetMethods->Set(Context, V8Name, v8::True(Isolate)));
}
#endif
TypeScriptGeneratedClass->FunctionToRedirect.Add(FunctionFName);
TypeScriptGeneratedClass->RedirectToTypeScript(Function);
}
}
}
TypeScriptGeneratedClass->RedirectedToTypeScript = true;
#if WITH_EDITOR
TypeScriptGeneratedClass->FunctionToRedirectInitialized = true;
#endif
TryReleaseType(TypeScriptGeneratedClass);
auto NativeCtor = GetJsClass(TypeScriptGeneratedClass, Context);
//如果已经有了bindinfoptr(此时forcereinject一定是true),这种情况下prototype是已经设置过了的,因此不用重复设置prototype
if (!(BindInfoPtr && ForceReinject))
{
v8::Local<v8::Value> VNativeProto;
if (NativeCtor->Get(Context, FV8Utils::ToV8String(Isolate, "prototype")).ToLocal(&VNativeProto) &&
VNativeProto->IsObject())
{
//{} -> Native Prototype -> Js Prototype -> Super Prototype
v8::Local<v8::Object> NativeProto = VNativeProto.As<v8::Object>();
__USE(BindInfoMap[TypeScriptGeneratedClass].Prototype.Get(Isolate)->SetPrototype(
Context, NativeProto));
if (SuperClass)
{
__USE(Proto->SetPrototype(Context, BindInfoMap[SuperClass].Prototype.Get(Isolate)));
}
else
{
__USE(Proto->SetPrototype(Context, NativeProto->GetPrototype()));
}
__USE(NativeProto->SetPrototype(Context, Proto));
#if !PUERTS_FORCE_CPP_UFUNCTION
v8::Local<v8::Value> MergeArgs[] = {Proto, NativeProto, NetMethods};
__USE(MergePrototype.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 3, MergeArgs));
#endif
}
else
{
__USE(BindInfoMap[TypeScriptGeneratedClass].Prototype.Get(Isolate)->SetPrototype(Context, Proto));
}
}
if (RebindObject)
{
for (FUEObjectIterator It(TypeScriptGeneratedClass); It; ++It)
{
auto Object = *It;
if (ObjectMap.Find(Object))
continue;
if (Object->GetClass()->GetName().StartsWith(TEXT("REINST_")))
continue; //跳过父类重新编译后临时状态的对象
//在编辑器下重启虚拟机,如果TS带构造函数,不重新执行的话,新虚拟机上逻辑上少执行了逻辑(比如对js对象一些字段的初始化)
//执行的话,对CreateDefaultSubobject这类UE逻辑又不允许执行多次(会崩溃),两者相较取其轻
//后面看是否能参照蓝图的组件初始化进行改造
// TsConstruct(TypeScriptGeneratedClass, Object);
__USE(FindOrAdd(Isolate, Context, Object->GetClass(), Object, true));
}
}
if (TypeScriptGeneratedClass->IsChildOf(UBlueprintFunctionLibrary::StaticClass()))
{
FinishInjection(TypeScriptGeneratedClass);
}
}
}
}
else
{
Logger->Error(FString::Printf(TEXT("module [%s] invalid"), *ModuleName));
}
}
else
{
Logger->Warn(FString::Printf(TEXT("not find module info for [%s]"), *TypeScriptGeneratedClass->GetName()));
}
}
}
#endif
void FJsEnvImpl::JsHotReload(FName ModuleName, const FString& JsSource)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
auto LocalReloadJs = ReloadJs.Get(Isolate);
FString OutPath, OutDebugPath;
if (ModuleLoader->Search(TEXT(""), ModuleName.ToString(), OutPath, OutDebugPath))
{
OutPath = FPaths::ConvertRelativePathToFull(OutPath);
Logger->Info(FString::Printf(TEXT("reload js module [%s]"), *OutPath));
v8::TryCatch TryCatch(Isolate);
v8::Handle<v8::Value> Args[] = {FV8Utils::ToV8String(Isolate, ModuleName), FV8Utils::ToV8String(Isolate, OutPath),
FV8Utils::ToV8String(Isolate, JsSource)};
(void) (LocalReloadJs->Call(Context, v8::Undefined(Isolate), 3, Args));
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("reload module exception %s"), *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
else
{
Logger->Warn(FString::Printf(TEXT("not find js module [%s]"), *ModuleName.ToString()));
return;
}
}
void FJsEnvImpl::ReloadModule(FName ModuleName, const FString& JsSource)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
// Logger->Info(FString::Printf(TEXT("start reload js module [%s]"), *ModuleName.ToString()));
JsHotReload(ModuleName, JsSource);
}
void FJsEnvImpl::ReloadSource(const FString& Path, const std::string& JsSource)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
auto LocalReloadJs = ReloadJs.Get(Isolate);
Logger->Info(FString::Printf(TEXT("reload js [%s]"), *Path));
v8::TryCatch TryCatch(Isolate);
v8::Handle<v8::Value> Args[] = {
v8::Undefined(Isolate), FV8Utils::ToV8String(Isolate, Path), FV8Utils::ToV8String(Isolate, JsSource.c_str())};
(void) (LocalReloadJs->Call(Context, v8::Undefined(Isolate), 3, Args));
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("reload module exception %s"), *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
void FJsEnvImpl::OnSourceLoaded(std::function<void(const FString&)> Callback)
{
OnSourceLoadedCallback = Callback;
}
#if !defined(ENGINE_INDEPENDENT_JSENV)
void FJsEnvImpl::TryBindJs(const class UObjectBase* InObject)
{
UObjectBaseUtility* Object = static_cast<UObjectBaseUtility*>(const_cast<UObjectBase*>(InObject));
const bool IsCDO = Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject);
// if (!Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
{
UClass* Class = InObject->GetClass();
auto TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(Class);
if (UNLIKELY(TypeScriptGeneratedClass))
{
if (UNLIKELY(IsCDO))
{
if (TypeScriptGeneratedClass->IsChildOf(UBlueprintFunctionLibrary::StaticClass()))
{
TypeScriptGeneratedClass->DynamicInvoker = TsDynamicInvoker;
TypeScriptGeneratedClass->ClassConstructor = &UTypeScriptGeneratedClass::StaticConstructor;
for (TFieldIterator<UFunction> FuncIt(TypeScriptGeneratedClass, EFieldIteratorFlags::ExcludeSuper); FuncIt;
++FuncIt)
{
auto Function = *FuncIt;
if (!Function->HasAllFunctionFlags(FUNC_Static) || Function->HasAnyFunctionFlags(FUNC_Native))
{
continue;
}
Function->FunctionFlags |= FUNC_BlueprintCallable | FUNC_BlueprintEvent | FUNC_Public | FUNC_Native;
Function->SetNativeFunc(&UTypeScriptGeneratedClass::execLazyLoadCallJS);
TypeScriptGeneratedClass->AddNativeFunction(
*Function->GetName(), &UTypeScriptGeneratedClass::execLazyLoadCallJS);
}
}
else
{
// MakeSureInject(TypeScriptGeneratedClass, true, true);
TypeScriptGeneratedClass->DynamicInvoker = TsDynamicInvoker;
TypeScriptGeneratedClass->ClassConstructor = &UTypeScriptGeneratedClass::StaticConstructor;
if (IsInGameThread())
{
// 其实目前在编辑器下Start后才启动虚拟机,这部分本来防止蓝图刷新的代码其实用不上了
auto BindInfoPtr = BindInfoMap.Find(TypeScriptGeneratedClass);
if (BindInfoPtr)
{
BindInfoPtr->InjectNotFinished = true; // CDO construct meat first load or recompiled
}
}
}
}
}
else if (UNLIKELY(!IsCDO && Class == UTypeScriptGeneratedClass::StaticClass()))
{
TypeScriptGeneratedClass = static_cast<UTypeScriptGeneratedClass*>(Object);
TypeScriptGeneratedClass->DynamicInvoker = TsDynamicInvoker;
TypeScriptGeneratedClass->ClassConstructor = &UTypeScriptGeneratedClass::StaticConstructor;
if (IsInGameThread())
{
auto BindInfoPtr = BindInfoMap.Find(TypeScriptGeneratedClass);
if (BindInfoPtr)
{
BindInfoPtr->InjectNotFinished = true; // CDO construct meat first load or recompiled
}
}
}
}
}
void FJsEnvImpl::RebindJs()
{
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* Class = *It;
if (!Class->IsNative())
{
if (auto TsClass = Cast<UTypeScriptGeneratedClass>(Class))
{
if (!TsClass->NotSupportInject())
{
#if WITH_EDITOR
if (TsClass->FunctionToRedirectInitialized && !TsClass->IsChildOf(UBlueprintFunctionLibrary::StaticClass()))
{
TsClass->DynamicInvoker = TsDynamicInvoker;
TsClass->ClassConstructor = &UTypeScriptGeneratedClass::StaticConstructor;
TsClass->NeedReBind = true;
TsClass->GeneratedObjects.Empty(TsClass->GeneratedObjects.Num());
TsClass->LazyLoadRedirect();
}
else
#endif
{
MakeSureInject(TsClass, false, true);
FinishInjection(TsClass);
}
}
}
else
{
auto IsTsSubclass = [](UClass* InnerClass)
{
while (InnerClass)
{
if (InnerClass->ClassConstructor == UTypeScriptGeneratedClass::StaticConstructor ||
Cast<UTypeScriptGeneratedClass>(InnerClass))
return true;
InnerClass = InnerClass->GetSuperClass();
}
return false;
};
if (IsTsSubclass(Class))
{
Class->ClassConstructor = UTypeScriptGeneratedClass::StaticConstructor;
}
}
}
}
//如果在notifyrebind的时候去遍历,那么多次遍历会明显拉低性能
//改为rebindjs的时候一次性找到全部需要rebind的object
//在遍历之后创建的uobject,虽然不会被GeneratedObjects记录,但是因为会走TypeScript::ObjectInialize,所以也不用担心没有被bind
#if WITH_EDITOR
for (TObjectIterator<UObject> It; It; ++It)
{
if (It->GetClass()->ClassConstructor == UTypeScriptGeneratedClass::StaticConstructor)
{
UClass* Cls = It->GetClass();
while (Cls)
{
if (Cast<UTypeScriptGeneratedClass>(Cls))
break;
Cls = Cls->GetSuperClass();
}
while (Cls)
{
if (UTypeScriptGeneratedClass* TsClass = Cast<UTypeScriptGeneratedClass>(Cls))
{
TsClass->GeneratedObjects.Add(*It);
Cls = Cls->GetSuperClass();
}
else
{
break;
}
}
}
}
#endif
}
#endif
FString FJsEnvImpl::CurrentStackTrace()
{
#ifndef WITH_QUICKJS
v8::Isolate* Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
std::string StackTrace = StackTraceToString(Isolate, v8::StackTrace::CurrentStackTrace(Isolate, 10, v8::StackTrace::kDetailed));
return UTF8_TO_TCHAR(StackTrace.c_str());
#else
return TEXT("");
#endif
}
bool FJsEnvImpl::IsTypeScriptGeneratedClass(UClass* Class)
{
while (Class)
{
if (Class->GetClass() == UTypeScriptGeneratedClass::StaticClass())
{
return true;
}
Class = Class->GetSuperClass();
}
return false;
}
void FJsEnvImpl::Bind(FClassWrapper* ClassWrapper, UObject* UEObject,
v8::Local<v8::Object> JSObject) // Just call in FClassReflection::Call, new a Object
{
#if PUERTS_KEEP_UOBJECT_REFERENCE
const bool IsNativeTakeJsRef = ClassWrapper->IsNativeTakeJsRef;
#else
const bool ClassWrapperIsNativeTakeJsRef = ClassWrapper->IsNativeTakeJsRef; //这个值只有mixin会进行设置
const bool IsUClass = UEObject->IsA<UClass>();
const bool IsCDO = UEObject->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject);
const bool IsNativeTakeJsRef = (IsCDO || IsUClass) ? false : ClassWrapperIsNativeTakeJsRef;
#endif
DataTransfer::SetPointer(MainIsolate, JSObject, UEObject, 0);
DataTransfer::SetPointer(MainIsolate, JSObject, nullptr, 1);
ObjectMap.Emplace(UEObject, v8::UniquePersistent<v8::Value>(MainIsolate, JSObject));
if (!IsNativeTakeJsRef)
{
SetJsTakeRef(UEObject, ClassWrapper);
}
}
void FJsEnvImpl::SetJsTakeRef(UObject* UEObject, FClassWrapper* ClassWrapper)
{
UserObjectRetainer.Retain(UEObject);
ObjectMap[UEObject].SetWeak<UClass>(
Cast<UClass>(ClassWrapper->Struct.Get()), FClassWrapper::OnGarbageCollected, v8::WeakCallbackType::kInternalFields);
}
void FJsEnvImpl::UnBind(UClass* Class, UObject* UEObject, bool ResetPointer)
{
auto PersistentValuePtr = ObjectMap.Find(UEObject);
if (PersistentValuePtr)
{
if (ResetPointer)
{
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
auto JsObject = PersistentValuePtr->Get(Isolate).As<v8::Object>();
DataTransfer::SetPointer(MainIsolate, JsObject, RELEASED_UOBJECT, 0);
DataTransfer::SetPointer(Isolate, JsObject, nullptr, 1);
}
ObjectMap.Remove(UEObject);
UserObjectRetainer.Release(UEObject);
}
}
void FJsEnvImpl::UnBind(UClass* Class, UObject* UEObject)
{
UnBind(Class, UEObject, false);
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAdd(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, UClass* Class, UObject* UEObject, bool SkipTypeScriptInitial)
{
if (!UEObject)
{
return v8::Null(Isolate);
}
auto PersistentValuePtr = ObjectMap.Find(UEObject);
if (!PersistentValuePtr) // create and link
{
bool Existed;
auto TemplateInfoPtr = GetTemplateInfoOfType(Class, Existed);
auto Result = TemplateInfoPtr->Template.Get(Isolate)->InstanceTemplate()->NewInstance(Context).ToLocalChecked();
auto ClassWrapper = static_cast<FClassWrapper*>(TemplateInfoPtr->StructWrapper.get());
Bind(ClassWrapper, UEObject, Result);
if (!SkipTypeScriptInitial && ClassWrapper->IsTypeScriptGeneratedClass)
{
TypeScriptInitial(UEObject->GetClass(), UEObject);
}
return Result;
}
else
{
return v8::Local<v8::Value>::New(Isolate, *PersistentValuePtr);
}
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAdd(v8::Isolate* Isolate, v8::Local<v8::Context>& Context, UClass* Class, UObject* UEObject)
{
return FindOrAdd(Isolate, Context, Class, UEObject, false);
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAddStruct(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, UScriptStruct* ScriptStruct, void* Ptr, bool PassByPointer)
{
if (!Ptr)
{
return v8::Null(Isolate);
}
auto HeaderPtr = StructCache.Find(Ptr);
if (HeaderPtr)
{
auto CacheNodePtr = HeaderPtr->Find(ScriptStruct);
if (CacheNodePtr)
{
return CacheNodePtr->Value.Get(Isolate);
}
}
// create and link
bool Existed;
auto TemplateInfoPtr = GetTemplateInfoOfType(ScriptStruct, Existed);
auto Result = TemplateInfoPtr->Template.Get(Isolate)->InstanceTemplate()->NewInstance(Context).ToLocalChecked();
BindStruct(static_cast<FScriptStructWrapper*>(TemplateInfoPtr->StructWrapper.get()), Ptr, Result, PassByPointer);
return Result;
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAddCppObject(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, const void* TypeId, void* Ptr, bool PassByPointer)
{
return CppObjectMapper.FindOrAddCppObject(Isolate, Context, TypeId, Ptr, PassByPointer);
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAddDelegate(v8::Isolate* Isolate, v8::Local<v8::Context>& Context, UObject* Owner,
PropertyMacro* Property, void* DelegatePtr, bool PassByPointer)
{
check(DelegatePtr); // must not null
if (PassByPointer)
{
auto Iter = DelegateMap.find(DelegatePtr);
if (Iter != DelegateMap.end())
{
if (Iter->second.Owner.IsValid())
{
return Iter->second.JSObject.Get(Isolate);
}
else
{
ClearDelegate(Isolate, Context, DelegatePtr);
}
}
}
else
{
if (CastFieldMacro<DelegatePropertyMacro>(Property))
{
auto NewDelegatePtr = new FScriptDelegate;
*NewDelegatePtr = *static_cast<FScriptDelegate*>(DelegatePtr);
DelegatePtr = NewDelegatePtr;
}
else // do not support MulticastDelegate
{
return v8::Undefined(Isolate);
}
}
{
// UE_LOG(LogTemp, Warning, TEXT("FindOrAddDelegate -- new %s"), *Property->GetName());
auto Constructor = (Property->IsA<DelegatePropertyMacro>() ? DelegateTemplate : MulticastDelegateTemplate)
.Get(Isolate)
->GetFunction(Context)
.ToLocalChecked();
auto JSObject = Constructor->NewInstance(Context).ToLocalChecked();
DataTransfer::SetPointer(Isolate, JSObject, DelegatePtr, 0);
(void) (JSObject->Set(Context, 0, v8::Map::New(Isolate)));
UFunction* Function = nullptr;
DelegatePropertyMacro* DelegateProperty = CastFieldMacro<DelegatePropertyMacro>(Property);
MulticastDelegatePropertyMacro* MulticastDelegateProperty = CastFieldMacro<MulticastDelegatePropertyMacro>(Property);
if (DelegateProperty)
{
Function = DelegateProperty->SignatureFunction;
}
else if (MulticastDelegateProperty)
{
Function = MulticastDelegateProperty->SignatureFunction;
}
DelegateMap[DelegatePtr] = {v8::UniquePersistent<v8::Object>(Isolate, JSObject), TWeakObjectPtr<UObject>(Owner),
DelegateProperty, MulticastDelegateProperty, Function, PassByPointer, nullptr,
v8::UniquePersistent<v8::Array>(Isolate, v8::Array::New(Isolate))};
return JSObject;
}
}
v8::Local<v8::Value> FJsEnvImpl::CreateArray(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, FPropertyTranslator* Property, void* ArrayPtr)
{
auto Array = FixSizeArrayTemplate.Get(Isolate)->GetFunction(Context).ToLocalChecked()->NewInstance(Context).ToLocalChecked();
DataTransfer::SetPointer(Isolate, Array, ArrayPtr, 0);
DataTransfer::SetPointer(Isolate, Array, Property, 1);
return Array;
}
void FJsEnvImpl::InvokeDelegateCallback(UDynamicDelegateProxy* Proxy, void* Params)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
auto SignatureFunction = Proxy->SignatureFunction;
auto Iter = JsCallbackPrototypeMap.find(SignatureFunction.Get());
if (Iter == JsCallbackPrototypeMap.end())
{
if (!SignatureFunction.IsValid())
{
Logger->Warn(TEXT("invalid SignatureFunction!"));
return;
}
JsCallbackPrototypeMap[SignatureFunction.Get()] = std::make_unique<FFunctionTranslator>(SignatureFunction.Get(), true);
Iter = JsCallbackPrototypeMap.find(SignatureFunction.Get());
}
else
{
if (!SignatureFunction.IsValid())
{
JsCallbackPrototypeMap.erase(Iter);
Logger->Warn(TEXT("invalid SignatureFunction!"));
return;
}
// 非 Editor 模式,函数签名地址可能会变且内存可能复用,不检查可能会访问到旧的非法地址。
if (!Iter->second->IsValid())
{
JsCallbackPrototypeMap[SignatureFunction.Get()] = std::make_unique<FFunctionTranslator>(SignatureFunction.Get(), true);
Iter = JsCallbackPrototypeMap.find(SignatureFunction.Get());
}
}
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
v8::TryCatch TryCatch(Isolate);
Iter->second->CallJs(Isolate, Context, Proxy->JsFunction.Get(Isolate), Context->Global(), Params);
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("js callback exception %s"), *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
#if !defined(ENGINE_INDEPENDENT_JSENV)
void FJsEnvImpl::JsConstruct(UClass* Class, UObject* Object, const v8::UniquePersistent<v8::Function>& Constructor,
const v8::UniquePersistent<v8::Object>& Prototype)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
v8::TryCatch TryCatch(Isolate);
auto JSObject = FindOrAdd(Isolate, Context, Class, Object)->ToObject(Context).ToLocalChecked();
// 过时功能(makeUClass)用不影响现有功能的方式修改
UnBind(Class, Object);
ObjectMap.Emplace(Object, v8::UniquePersistent<v8::Value>(MainIsolate, JSObject));
if (!Prototype.IsEmpty())
{
(void) (JSObject->SetPrototype(Context, Prototype.Get(Isolate)));
}
if (!Constructor.IsEmpty())
{
(void) (Constructor.Get(Isolate)->Call(Context, JSObject, 0, nullptr));
}
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("js callback exception %s"), *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
void FJsEnvImpl::TsConstruct(UTypeScriptGeneratedClass* Class, UObject* Object)
{
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#else
if (!IsInGameThread())
{
Logger->Error(FString::Printf(TEXT("Construct TypeScript Object %s(%p) on illegal thread!"), *Object->GetName(), Object));
}
#endif
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
bool IsCDO = Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject);
UObject* CDO = Class->GetDefaultObject(false);
if (CDO != Object && IsCDO) // blueprint extend a ts
{
UClass* ObjClass = Object->GetClass();
while (ObjClass && !ObjClass->IsNative() && !static_cast<UObject*>(ObjClass)->IsA<UTypeScriptGeneratedClass>())
{
// Logger->Warn(FString::Printf(TEXT("release %s in %s(%p) construct"), *ObjClass->GetName(), *Object->GetName(),
// Object));
TryReleaseType(ObjClass);
ObjClass = ObjClass->GetSuperClass();
}
}
auto BindInfoPtr = BindInfoMap.Find(Class);
if (!BindInfoPtr || BindInfoPtr->InjectNotFinished)
{
// Logger->Warn(FString::Printf(TEXT("force %s injection in %s(%p) construct"), *Class->GetName(), *Object->GetName(),
// Object));
MakeSureInject(Class, true, false);
if (!IsCDO) // finish inject in first non-CDO construct
{
// Logger->Warn(FString::Printf(TEXT("finish %s injection in %s(%p) construct"), *Class->GetName(), *Object->GetName(),
// Object));
FinishInjection(Class);
}
BindInfoPtr = BindInfoMap.Find(Class);
}
if (BindInfoPtr)
{
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
v8::TryCatch TryCatch(Isolate);
v8::Local<v8::Object> JSObject;
auto PersistentValuePtr = ObjectMap.Find(Object);
if (!PersistentValuePtr)
{
JSObject = FindOrAdd(Isolate, Context, Object->GetClass(), Object, true)->ToObject(Context).ToLocalChecked();
// FindOrAdd may change BindInfoMap, cause a rehash
BindInfoPtr = BindInfoMap.Find(Class);
}
else
{
JSObject = PersistentValuePtr->Get(Isolate).As<v8::Object>();
}
//假如是UTypeScriptGeneratedClass的对象,设置成间接Prototype,后续刷新代码对象会自动更新
if (Object->GetClass() == Class && !BindInfoPtr->Prototype.IsEmpty())
{
__USE(JSObject->SetPrototype(Context, BindInfoPtr->Prototype.Get(Isolate)));
}
if (!BindInfoPtr->Constructor.IsEmpty())
{
__USE(BindInfoPtr->Constructor.Get(Isolate)->Call(Context, JSObject, 0, nullptr));
}
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("js callback exception %s"), *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
else if (!Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
{
Logger->Error(FString::Printf(TEXT("Construct TypeScript Object fail for %s"), *Class->GetName()));
}
}
#endif
void FJsEnvImpl::NotifyUObjectDeleted(const class UObjectBase* ObjectBase, int32 Index)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
TryReleaseType((UStruct*) ObjectBase);
#if !defined(ENGINE_INDEPENDENT_JSENV)
BindInfoMap.Remove((UTypeScriptGeneratedClass*) ObjectBase);
#endif
UnBind(nullptr, (UObject*) ObjectBase, true);
GeneratedClasses.Remove((UClass*) ObjectBase);
TsFunctionMap.Remove((UFunction*) ObjectBase);
MixinFunctionMap.Remove((UFunction*) ObjectBase);
ContainerMeta.NotifyElementTypeDeleted((UField*) ObjectBase);
auto CallbacksPtr = AutoReleaseCallbacksMap.Find((UObject*) ObjectBase);
if (CallbacksPtr)
{
for (auto Callback : *CallbacksPtr)
{
if (Callback.IsValid())
{
Callback.Get()->JsFunction.Reset();
}
SysObjectRetainer.Release(Callback.Get());
}
AutoReleaseCallbacksMap.Remove((UObject*) ObjectBase);
}
}
void FJsEnvImpl::TryReleaseType(UStruct* Struct)
{
TypeToTemplateInfoMap.Remove(Struct);
}
// fix ScriptCore.cpp UObject::SkipFunction crash when Function has no parameters
static void SkipFunction(FFrame& Stack, RESULT_DECL, UFunction* Function)
{
uint8* Frame = (uint8*) FMemory_Alloca(Function->PropertiesSize);
FMemory::Memzero(Frame, Function->PropertiesSize);
for (PropertyMacro* Property = (PropertyMacro*) (
#if ENGINE_MINOR_VERSION >= 25 || ENGINE_MAJOR_VERSION > 4
Function->ChildProperties
#else
Function->Children
#endif
);
Property && (*Stack.Code != EX_EndFunctionParms); Property = (PropertyMacro*) (Property->Next))
{
Stack.MostRecentPropertyAddress = NULL;
Stack.Step(Stack.Object, (Property->PropertyFlags & CPF_OutParm) ? NULL : Property->ContainerPtrToValuePtr<uint8>(Frame));
}
Stack.Code++;
for (PropertyMacro* Destruct = Function->DestructorLink; Destruct; Destruct = Destruct->DestructorLinkNext)
{
if (!Destruct->HasAnyPropertyFlags(CPF_OutParm))
{
Destruct->DestroyValue_InContainer(Frame);
}
}
PropertyMacro* ReturnProp = Function->GetReturnProperty();
if (ReturnProp != NULL)
{
ReturnProp->DestroyValue(RESULT_PARAM);
FMemory::Memzero(RESULT_PARAM, ReturnProp->ArrayDim * ReturnProp->ElementSize);
}
}
#if !defined(ENGINE_INDEPENDENT_JSENV)
void FJsEnvImpl::InvokeJsMethod(UObject* ContextObject, UJSGeneratedFunction* Function, FFrame& Stack, void* RESULT_PARAM)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
v8::Local<v8::Value> Self;
auto GeneratedObjectPtr = ObjectMap.Find(ContextObject);
if (GeneratedObjectPtr)
{
Self = GeneratedObjectPtr->Get(Isolate);
}
if (Self.IsEmpty())
{
Logger->Error(FString::Printf(TEXT("call %s::%s of %p fail: can not find Binded JavaScript Object"),
*ContextObject->GetClass()->GetName(), *Function->GetName(), ContextObject));
SkipFunction(Stack, RESULT_PARAM, Function);
return;
}
v8::TryCatch TryCatch(Isolate);
Function->FunctionTranslator->CallJs(
Isolate, Context, Function->JsFunction.Get(Isolate), Self, ContextObject, Stack, RESULT_PARAM);
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("call %s::%s of %p fail: %s"), *ContextObject->GetClass()->GetName(),
*Function->GetName(), ContextObject, *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
void FJsEnvImpl::InvokeMixinMethod(UObject* ContextObject, UJSGeneratedFunction* Function, FFrame& Stack, void* RESULT_PARAM)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
v8::Local<v8::Value> Self = FindOrAdd(Isolate, Context, ContextObject->GetClass(), ContextObject);
auto JsFuncPtr = MixinFunctionMap.Find(Function);
if (!JsFuncPtr)
{
Logger->Error(FString::Printf(TEXT("call %s::%s of %p fail: can not find Binded JavaScript Function"),
*ContextObject->GetClass()->GetName(), *Function->GetName(), ContextObject));
SkipFunction(Stack, RESULT_PARAM, Function);
return;
}
v8::TryCatch TryCatch(Isolate);
Function->FunctionTranslator->CallJs(Isolate, Context, JsFuncPtr->Get(Isolate), Self, ContextObject, Stack, RESULT_PARAM);
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("call %s::%s of %p fail: %s"), *ContextObject->GetClass()->GetName(),
*Function->GetName(), ContextObject, *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
void FJsEnvImpl::TypeScriptInitial(UClass* Class, UObject* Object, const bool TypeScriptClassFound)
{
if (auto TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(Class))
{
TypeScriptInitial(Class->GetSuperClass(), Object, true);
TsConstruct(TypeScriptGeneratedClass, Object);
}
else if (!TypeScriptClassFound)
{
TypeScriptInitial(Class->GetSuperClass(), Object, false);
}
}
void FJsEnvImpl::InvokeTsMethod(UObject* ContextObject, UFunction* Function, FFrame& Stack, void* RESULT_PARAM)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
auto FuncInfo = TsFunctionMap.Find(Function);
if (!FuncInfo)
{
auto Class = Cast<UTypeScriptGeneratedClass>(Function->GetOuterUClassUnchecked());
MakeSureInject(Class, true, false);
FinishInjection(Class);
FuncInfo = TsFunctionMap.Find(Function);
if (!FuncInfo)
{
Logger->Error(FString::Printf(TEXT("call %s::%s of %p fail: can not find Binded JavaScript Function"),
*ContextObject->GetClass()->GetName(), *Function->GetName(), ContextObject));
SkipFunction(Stack, RESULT_PARAM, Function);
return;
}
}
{
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
v8::Local<v8::Value> ThisObj = v8::Undefined(Isolate);
if (!Function->HasAnyFunctionFlags(FUNC_Static))
{
ThisObj = FindOrAdd(Isolate, Context, ContextObject->GetClass(), ContextObject);
}
v8::TryCatch TryCatch(Isolate);
FuncInfo->FunctionTranslator->CallJs(
Isolate, Context, FuncInfo->JsFunction.Get(Isolate), ThisObj, ContextObject, Stack, RESULT_PARAM);
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(TEXT("call %s::%s of %p fail: %s"), *ContextObject->GetClass()->GetName(),
*Function->GetName(), ContextObject, *FV8Utils::TryCatchToString(Isolate, &TryCatch)));
}
}
}
void FJsEnvImpl::NotifyReBind(UTypeScriptGeneratedClass* Class)
{
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
MakeSureInject(Class, false, false);
FinishInjection(Class);
#if WITH_EDITOR
while (UTypeScriptGeneratedClass* SuperCls = Cast<UTypeScriptGeneratedClass>(Class->GetSuperClass()))
{
Class = SuperCls;
}
{
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
for (TWeakObjectPtr<UObject>& Iter : Class->GeneratedObjects)
{
auto Object = Iter.Get();
if (!Object || ObjectMap.Find(Object))
continue;
if (Object->GetClass()->GetName().StartsWith(TEXT("REINST_")))
continue; //跳过父类重新编译后临时状态的对象
__USE(FindOrAdd(Isolate, Context, Object->GetClass(), Object, true));
UTypeScriptGeneratedClass* ClassMayNeedReBind = nullptr;
auto TempClass = Object->GetClass();
while (TempClass && (TempClass != Class) && (!ClassMayNeedReBind))
{
ClassMayNeedReBind = Cast<UTypeScriptGeneratedClass>(TempClass);
TempClass = TempClass->GetSuperClass();
}
if (ClassMayNeedReBind)
{
MakeSureInject(ClassMayNeedReBind, false, false);
}
}
}
#endif
}
#endif
void FJsEnvImpl::ExecuteDelegate(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, const v8::FunctionCallbackInfo<v8::Value>& Info, void* DelegatePtr)
{
auto Iter = DelegateMap.find(DelegatePtr);
if (Iter == DelegateMap.end())
{
FV8Utils::ThrowException(Isolate, "can not find the delegate!");
}
auto SignatureFunction = Iter->second.SignatureFunction;
if (JsCallbackPrototypeMap.find(SignatureFunction) == JsCallbackPrototypeMap.end())
{
JsCallbackPrototypeMap[SignatureFunction] = std::make_unique<FFunctionTranslator>(SignatureFunction, true);
}
if (Iter->second.DelegateProperty)
{
JsCallbackPrototypeMap[SignatureFunction]->Call(Isolate, Context, Info,
[ScriptDelegate = static_cast<FScriptDelegate*>(DelegatePtr)](void* Params)
{ ScriptDelegate->ProcessDelegate<UObject>(Params); });
}
else
{
JsCallbackPrototypeMap[SignatureFunction]->Call(Isolate, Context, Info,
[MulticastScriptDelegate = static_cast<FMulticastScriptDelegate*>(DelegatePtr)](void* Params)
{ MulticastScriptDelegate->ProcessMulticastDelegate<UObject>(Params); });
}
}
static FName NAME_Fire("Fire");
bool FJsEnvImpl::AddToDelegate(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, void* DelegatePtr, v8::Local<v8::Function> JsFunction)
{
// UE_LOG(LogTemp, Warning, TEXT("add delegate proxy"));
auto Iter = DelegateMap.find(DelegatePtr);
if (Iter == DelegateMap.end())
{
return false;
}
if (!Iter->second.Owner.IsValid())
{
Logger->Warn("try to bind a delegate with invalid owner!");
ClearDelegate(Isolate, Context, DelegatePtr);
if (!Iter->second.PassByPointer)
{
delete ((FScriptDelegate*) Iter->first);
}
DelegateMap.erase(Iter);
return false;
}
auto JsCallbacks = Iter->second.JsCallbacks.Get(Isolate);
UDynamicDelegateProxy* DelegateProxy = nullptr;
bool InitApplyFunc = false;
if (Iter->second.Proxy.IsValid())
{
DelegateProxy = Iter->second.Proxy.Get();
if (Iter->second.DelegateProperty)
{
if (!static_cast<FScriptDelegate*>(DelegatePtr)->IsBoundToObject(DelegateProxy))
{
InitApplyFunc = true;
}
}
else if (Iter->second.MulticastDelegateProperty)
{
#if ENGINE_MINOR_VERSION >= 23 || ENGINE_MAJOR_VERSION > 4
if (Iter->second.MulticastDelegateProperty->IsA<MulticastSparseDelegatePropertyMacro>())
{
if (!FSparseDelegateStorage::Contains(
Iter->second.Owner.Get(), Iter->second.MulticastDelegateProperty->GetFName(), DelegateProxy, NAME_Fire))
{
InitApplyFunc = true;
}
}
else
{
#endif
if (!static_cast<FMulticastScriptDelegate*>(DelegatePtr)->Contains(DelegateProxy, NAME_Fire))
{
InitApplyFunc = true;
}
}
}
if (InitApplyFunc)
{
if (JsCallbacks->Length() > 0)
{
JsCallbacks = v8::Array::New(Isolate);
Iter->second.JsCallbacks.Reset(Isolate, JsCallbacks);
}
else
{
InitApplyFunc = false;
}
}
}
else
{
DelegateProxy = NewObject<UDynamicDelegateProxy>();
#ifdef THREAD_SAFE
DelegateProxy->Isolate = Isolate;
#endif
DelegateProxy->Owner = Iter->second.Owner;
DelegateProxy->SignatureFunction = Iter->second.SignatureFunction;
DelegateProxy->DynamicInvoker = DynamicInvoker;
InitApplyFunc = true;
SysObjectRetainer.Retain(DelegateProxy);
Iter->second.Proxy = DelegateProxy;
}
if (InitApplyFunc)
{
v8::TryCatch TryCatch(Isolate);
v8::Local<v8::Value> Args[] = {JsCallbacks};
v8::Local<v8::Value> Apply;
if (!GenListApply.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 1, Args).ToLocal(&Apply) || !Apply->IsFunction())
{
FString ErrMsg = TEXT("Unknow");
if (TryCatch.HasCaught())
{
ErrMsg = FV8Utils::TryCatchToString(Isolate, &TryCatch);
}
Logger->Error(FString::Printf(TEXT("gen callback apply fail: %s"), *ErrMsg));
return false;
}
DelegateProxy->JsFunction = v8::UniquePersistent<v8::Function>(Isolate, v8::Local<v8::Function>::Cast(Apply));
}
bool bSingleDelegate = Iter->second.DelegateProperty != nullptr;
(void) (JsCallbacks->Set(Context, bSingleDelegate ? 0 : JsCallbacks->Length(), JsFunction)); // push
FScriptDelegate Delegate;
Delegate.BindUFunction(DelegateProxy, NAME_Fire);
if (Iter->second.DelegateProperty)
{
// UE_LOG(LogTemp, Warning, TEXT("bind to delegate"));
*(static_cast<FScriptDelegate*>(DelegatePtr)) = Delegate;
}
else if (Iter->second.MulticastDelegateProperty)
{
// UE_LOG(LogTemp, Warning, TEXT("add to multicast delegate, proxy: %p to:%p"), DelegateProxy, DelegatePtr);
#if ENGINE_MINOR_VERSION >= 23 || ENGINE_MAJOR_VERSION > 4
if (Iter->second.MulticastDelegateProperty->IsA<MulticastSparseDelegatePropertyMacro>())
{
Iter->second.MulticastDelegateProperty->AddDelegate(MoveTemp(Delegate), Iter->second.Owner.Get(), DelegatePtr);
}
else
#endif
{
static_cast<FMulticastScriptDelegate*>(DelegatePtr)->AddUnique(Delegate);
}
}
return true;
}
PropertyMacro* FJsEnvImpl::FindDelegateProperty(void* DelegatePtr)
{
auto Iter = DelegateMap.find(DelegatePtr);
if (Iter == DelegateMap.end())
{
return nullptr;
}
return Iter->second.DelegateProperty ? (PropertyMacro*) Iter->second.DelegateProperty
: (PropertyMacro*) Iter->second.MulticastDelegateProperty;
}
FScriptDelegate FJsEnvImpl::NewDelegate(v8::Isolate* Isolate, v8::Local<v8::Context>& Context, UObject* Owner,
v8::Local<v8::Function> JsFunction, UFunction* SignatureFunction)
{
UDynamicDelegateProxy* DelegateProxy = nullptr;
if (Owner)
{
TArray<TWeakObjectPtr<UDynamicDelegateProxy>>& Callbacks = AutoReleaseCallbacksMap.FindOrAdd(Owner);
DelegateProxy = NewObject<UDynamicDelegateProxy>();
#ifdef THREAD_SAFE
DelegateProxy->Isolate = Isolate;
#endif
DelegateProxy->Owner = Owner;
DelegateProxy->SignatureFunction = SignatureFunction;
DelegateProxy->DynamicInvoker = DynamicInvoker;
DelegateProxy->JsFunction = v8::UniquePersistent<v8::Function>(Isolate, JsFunction);
SysObjectRetainer.Retain(DelegateProxy);
Callbacks.Add(DelegateProxy);
}
else
{
auto CallbacksMap = ManualReleaseCallbackMap.Get(Isolate);
auto MaybeProxy = CallbacksMap->Get(Context, JsFunction);
if (MaybeProxy.IsEmpty() || !MaybeProxy.ToLocalChecked()->IsExternal())
{
DelegateProxy = NewObject<UDynamicDelegateProxy>();
#ifdef THREAD_SAFE
DelegateProxy->Isolate = Isolate;
#endif
DelegateProxy->Owner = DelegateProxy;
DelegateProxy->SignatureFunction = SignatureFunction;
DelegateProxy->DynamicInvoker = DynamicInvoker;
DelegateProxy->JsFunction = v8::UniquePersistent<v8::Function>(Isolate, JsFunction);
SysObjectRetainer.Retain(DelegateProxy);
__USE(CallbacksMap->Set(Context, JsFunction, v8::External::New(Context->GetIsolate(), DelegateProxy)));
ManualReleaseCallbackList.push_back(DelegateProxy);
}
else
{
DelegateProxy = Cast<UDynamicDelegateProxy>(
static_cast<UObject*>(v8::Local<v8::External>::Cast(MaybeProxy.ToLocalChecked())->Value()));
if (DelegateProxy->SignatureFunction.Get() != SignatureFunction)
{
Logger->Error(TEXT("aleady bind to another delegate pleace release first!"));
DelegateProxy = nullptr;
}
}
}
FScriptDelegate Delegate;
if (DelegateProxy)
{
Delegate.BindUFunction(DelegateProxy, NAME_Fire);
}
return Delegate;
}
void FJsEnvImpl::ReleaseManualReleaseDelegate(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgFunction);
auto CallbacksMap = ManualReleaseCallbackMap.Get(Isolate);
auto MaybeProxy = CallbacksMap->Get(Context, Info[0]);
if (!MaybeProxy.IsEmpty() && MaybeProxy.ToLocalChecked()->IsExternal())
{
__USE(CallbacksMap->Delete(Context, Info[0]));
auto DelegateProxy =
Cast<UDynamicDelegateProxy>(static_cast<UObject*>(v8::Local<v8::External>::Cast(MaybeProxy.ToLocalChecked())->Value()));
for (auto it = ManualReleaseCallbackList.begin(); it != ManualReleaseCallbackList.end();)
{
if (!it->IsValid())
{
it = ManualReleaseCallbackList.erase(it);
}
else if (it->Get() == DelegateProxy)
{
DelegateProxy->JsFunction.Reset();
it = ManualReleaseCallbackList.erase(it);
SysObjectRetainer.Release(DelegateProxy);
}
else
{
++it;
}
}
}
}
bool FJsEnvImpl::RemoveFromDelegate(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, void* DelegatePtr, v8::Local<v8::Function> JsFunction)
{
auto Iter = DelegateMap.find(DelegatePtr);
if (Iter == DelegateMap.end())
{
return false;
}
FScriptDelegate Delegate;
if (Iter->second.DelegateProperty)
{
return ClearDelegate(Isolate, Context, DelegatePtr);
}
else if (Iter->second.MulticastDelegateProperty)
{
auto JsCallbacks = Iter->second.JsCallbacks.Get(Isolate);
v8::Local<v8::Value> Args[] = {JsCallbacks, JsFunction};
__USE(RemoveListItem.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 2, Args));
if (JsCallbacks->Length() == 0 && Iter->second.Proxy.IsValid())
{
auto DelegateProxy = Iter->second.Proxy.Get();
Delegate.BindUFunction(DelegateProxy, NAME_Fire);
#if ENGINE_MINOR_VERSION >= 23 || ENGINE_MAJOR_VERSION > 4
if (Iter->second.MulticastDelegateProperty->IsA<MulticastSparseDelegatePropertyMacro>())
{
Iter->second.MulticastDelegateProperty->RemoveDelegate(Delegate, Iter->second.Owner.Get(), DelegatePtr);
}
else
#endif
{
static_cast<FMulticastScriptDelegate*>(DelegatePtr)->Remove(Delegate);
}
SysObjectRetainer.Release(DelegateProxy);
DelegateProxy->JsFunction.Reset();
Iter->second.Proxy.Reset();
}
}
return true;
}
bool FJsEnvImpl::ClearDelegate(v8::Isolate* Isolate, v8::Local<v8::Context>& Context, void* DelegatePtr)
{
auto Iter = DelegateMap.find(DelegatePtr);
if (Iter == DelegateMap.end())
{
return false;
}
auto JSObject = Iter->second.JSObject.Get(Isolate);
auto Map = v8::Local<v8::Map>::Cast(JSObject->Get(Context, 0).ToLocalChecked());
Map->Clear();
if (Iter->second.DelegateProperty)
{
if (Iter->second.Owner.IsValid())
{
FScriptDelegate Delegate;
*(static_cast<FScriptDelegate*>(DelegatePtr)) = Delegate;
}
}
else if (Iter->second.MulticastDelegateProperty)
{
if (Iter->second.Owner.IsValid())
{
#if ENGINE_MINOR_VERSION >= 23 || ENGINE_MAJOR_VERSION > 4
if (Iter->second.MulticastDelegateProperty->IsA<MulticastSparseDelegatePropertyMacro>())
{
Iter->second.MulticastDelegateProperty->ClearDelegate(Iter->second.Owner.Get(), DelegatePtr);
}
else
#endif
{
static_cast<FMulticastScriptDelegate*>(DelegatePtr)->Clear();
}
}
}
if (Iter->second.Proxy.IsValid())
{
Iter->second.Proxy->JsFunction.Reset();
SysObjectRetainer.Release(Iter->second.Proxy.Get());
Iter->second.Proxy.Reset();
}
Iter->second.JsCallbacks.Reset(Isolate, v8::Array::New(Isolate));
return true;
}
bool FJsEnvImpl::CheckDelegateProxies(float Tick)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
std::vector<void*> PendingToRemove;
for (auto& KV : DelegateMap)
{
if (!KV.second.Owner.IsValid())
{
PendingToRemove.push_back(KV.first);
}
}
if (PendingToRemove.size() > 0)
{
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
for (int i = 0; i < PendingToRemove.size(); ++i)
{
ClearDelegate(Isolate, Context, PendingToRemove[i]);
if (!DelegateMap[PendingToRemove[i]].PassByPointer)
{
delete ((FScriptDelegate*) PendingToRemove[i]);
}
DelegateMap.erase(PendingToRemove[i]);
}
}
// Collecting invalid function translators to remove.
std::vector<UFunction*> PendingToRemoveJsCallbacks;
for (auto& KV : JsCallbackPrototypeMap)
{
if ((nullptr == KV.first) || (!KV.second->IsValid()))
{
PendingToRemoveJsCallbacks.push_back(KV.first);
}
}
if (PendingToRemoveJsCallbacks.size() > 0)
{
for (int32 i = 0; i < PendingToRemoveJsCallbacks.size(); i++)
{
JsCallbackPrototypeMap.erase(PendingToRemoveJsCallbacks[i]);
}
}
return true;
}
FPropertyTranslator* FJsEnvImpl::GetContainerPropertyTranslator(PropertyMacro* Property)
{
auto Iter = ContainerPropertyMap.find(Property);
// TODO: 如果脚本一直持有蓝图里头的Map,还是有可能有问题的,需要统筹考虑一套机制解决这类问题
if (Iter == ContainerPropertyMap.end() || !Iter->second.PropertyWeakPtr.IsValid())
{
ContainerPropertyInfo Temp{Property, FPropertyTranslator::Create(Property)};
ContainerPropertyMap[Property] = std::move(Temp);
#if ENGINE_MINOR_VERSION < 25 && ENGINE_MAJOR_VERSION < 5
if (!Property->IsNative())
{
SysObjectRetainer.Retain(Property);
}
#endif
return ContainerPropertyMap[Property].PropertyTranslator.get();
}
else
{
return Iter->second.PropertyTranslator.get();
}
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAddContainer(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, PropertyMacro* Property, FScriptArray* Ptr, bool PassByPointer)
{
check(Ptr); // must not null
auto PersistentValuePtr = ContainerCache.Find(Ptr);
if (PersistentValuePtr)
{
return PersistentValuePtr->Container.Get(Isolate);
}
auto Result = ArrayTemplate.Get(Isolate)->InstanceTemplate()->NewInstance(Context).ToLocalChecked();
BindContainer(Ptr, Result,
PassByPointer ? FScriptArrayWrapper::OnGarbageCollected : FScriptArrayWrapper::OnGarbageCollectedWithFree, PassByPointer,
EArray);
DataTransfer::SetPointer(Isolate, Result, GetContainerPropertyTranslator(Property), 1);
return Result;
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAddContainer(
v8::Isolate* Isolate, v8::Local<v8::Context>& Context, PropertyMacro* Property, FScriptSet* Ptr, bool PassByPointer)
{
check(Ptr); // must not null
auto PersistentValuePtr = ContainerCache.Find(Ptr);
if (PersistentValuePtr)
{
return PersistentValuePtr->Container.Get(Isolate);
}
auto Result = SetTemplate.Get(Isolate)->InstanceTemplate()->NewInstance(Context).ToLocalChecked();
BindContainer(Ptr, Result,
PassByPointer ? FScriptSetWrapper::OnGarbageCollected : FScriptSetWrapper::OnGarbageCollectedWithFree, PassByPointer, ESet);
DataTransfer::SetPointer(Isolate, Result, GetContainerPropertyTranslator(Property), 1);
return Result;
}
v8::Local<v8::Value> FJsEnvImpl::FindOrAddContainer(v8::Isolate* Isolate, v8::Local<v8::Context>& Context,
PropertyMacro* KeyProperty, PropertyMacro* ValueProperty, FScriptMap* Ptr, bool PassByPointer)
{
check(Ptr); // must not null
auto PersistentValuePtr = ContainerCache.Find(Ptr);
if (PersistentValuePtr)
{
return PersistentValuePtr->Container.Get(Isolate);
}
auto Result = MapTemplate.Get(Isolate)->InstanceTemplate()->NewInstance(Context).ToLocalChecked();
BindContainer(Ptr, Result,
PassByPointer ? FScriptMapWrapper::OnGarbageCollected : FScriptMapWrapper::OnGarbageCollectedWithFree, PassByPointer, EMap);
DataTransfer::SetPointer(Isolate, Result, GetContainerPropertyTranslator(KeyProperty), 1);
DataTransfer::SetPointer(Isolate, Result, GetContainerPropertyTranslator(ValueProperty), 2);
return Result;
}
void FJsEnvImpl::BindStruct(
FScriptStructWrapper* ScriptStructWrapper, void* Ptr, v8::Local<v8::Object> JSObject, bool PassByPointer)
{
DataTransfer::SetPointer(MainIsolate, JSObject, Ptr, 0);
DataTransfer::SetPointer(
MainIsolate, JSObject, static_cast<UScriptStruct*>(ScriptStructWrapper->Struct.Get()), 1); // add type info
if (!PassByPointer)
{
// Optimization branch:
// ArrayBuffer will be gced on v8 worker thread
// for pod ustruct it's safe to use it
#if defined(HAS_ARRAYBUFFER_NEW_WITHOUT_STL) || defined(WITH_BACKING_STORE_AUTO_FREE)
bool bStructTriviallyFreed = false;
if (ScriptStructWrapper->Struct.IsValid())
{
UScriptStruct* Struct = Cast<UScriptStruct>(ScriptStructWrapper->Struct.Get());
bStructTriviallyFreed = Struct->StructFlags & EStructFlags::STRUCT_IsPlainOldData;
}
if (bStructTriviallyFreed)
{
#if defined(HAS_ARRAYBUFFER_NEW_WITHOUT_STL)
auto MemoryHolder = v8::ArrayBuffer_New_Without_Stl(
MainIsolate, Ptr, ScriptStructWrapper->Struct->GetStructureSize(),
[](void* Data, size_t Length, void* DeleterData)
{
// TFScriptStructWrapper存放在TypeReflectionMap中,Isolate先Dispose后,对象才跟着销毁
FScriptStructWrapper* StructInfo = static_cast<FScriptStructWrapper*>(DeleterData);
FScriptStructWrapper::Free(StructInfo->Struct, StructInfo->ExternalFinalize, Data);
},
ScriptStructWrapper);
__USE(JSObject->Set(MainIsolate->GetCurrentContext(), 0, MemoryHolder));
return; // early return
#elif WITH_BACKING_STORE_AUTO_FREE
auto Backing = v8::ArrayBuffer::NewBackingStore(
Ptr, ScriptStructWrapper->Struct->GetStructureSize(),
[](void* Data, size_t Length, void* DeleterData)
{
// TFScriptStructWrapper存放在TypeReflectionMap中,Isolate先Dispose后,对象才跟着销毁
FScriptStructWrapper* StructInfo = static_cast<FScriptStructWrapper*>(DeleterData);
FScriptStructWrapper::Free(StructInfo->Struct, StructInfo->ExternalFinalize, Data);
},
ScriptStructWrapper);
auto MemoryHolder = v8::ArrayBuffer::New(MainIsolate, std::move(Backing));
__USE(JSObject->Set(MainIsolate->GetCurrentContext(), 0, MemoryHolder));
return; // early return
#endif
}
#endif
auto CacheNodePtr = StructCache.Find(Ptr);
if (CacheNodePtr)
{
CacheNodePtr = CacheNodePtr->Add(ScriptStructWrapper->Struct.Get());
}
else
{
CacheNodePtr = &StructCache.Emplace(Ptr, FObjectCacheNode(ScriptStructWrapper->Struct.Get()));
}
CacheNodePtr->Value.Reset(MainIsolate, JSObject);
CacheNodePtr->UserData = ScriptStructWrapper;
CacheNodePtr->Value.SetWeak<FScriptStructWrapper>(
ScriptStructWrapper, FScriptStructWrapper::OnGarbageCollectedWithFree, v8::WeakCallbackType::kInternalFields);
}
else
{
auto CacheNodePtr = StructCache.Find(Ptr);
if (CacheNodePtr)
{
CacheNodePtr = CacheNodePtr->Add(ScriptStructWrapper->Struct.Get());
}
else
{
CacheNodePtr = &StructCache.Emplace(Ptr, FObjectCacheNode(ScriptStructWrapper->Struct.Get()));
}
CacheNodePtr->Value.Reset(MainIsolate, JSObject);
CacheNodePtr->Value.SetWeak<FScriptStructWrapper>(
ScriptStructWrapper, FScriptStructWrapper::OnGarbageCollected, v8::WeakCallbackType::kInternalFields);
}
}
void FJsEnvImpl::BindCppObject(
v8::Isolate* InIsolate, JSClassDefinition* ClassDefinition, void* Ptr, v8::Local<v8::Object> JSObject, bool PassByPointer)
{
CppObjectMapper.BindCppObject(InIsolate, ClassDefinition, Ptr, JSObject, PassByPointer);
}
void* FJsEnvImpl::GetPrivateData(v8::Local<v8::Context> Context, v8::Local<v8::Object> JSObject)
{
return CppObjectMapper.GetPrivateData(Context, JSObject);
}
void FJsEnvImpl::SetPrivateData(v8::Local<v8::Context> Context, v8::Local<v8::Object> JSObject, void* Ptr)
{
CppObjectMapper.SetPrivateData(Context, JSObject, Ptr);
}
v8::MaybeLocal<v8::Function> FJsEnvImpl::LoadTypeById(v8::Local<v8::Context> Context, const void* TypeId)
{
return CppObjectMapper.LoadTypeById(Context, TypeId);
}
void FJsEnvImpl::UnBindStruct(FScriptStructWrapper* ScriptStructWrapper, void* Ptr)
{
auto CacheNodePtr = StructCache.Find(Ptr);
if (CacheNodePtr)
{
(void) (CacheNodePtr->Remove(ScriptStructWrapper->Struct.Get(), true));
if (!CacheNodePtr->TypeId) // last one
{
StructCache.Remove(Ptr);
}
}
}
void FJsEnvImpl::UnBindCppObject(v8::Isolate* Isolate, JSClassDefinition* ClassDefinition, void* Ptr)
{
CppObjectMapper.UnBindCppObject(Isolate, ClassDefinition, Ptr);
}
void FJsEnvImpl::BindContainer(void* Ptr, v8::Local<v8::Object> JSObject, void (*Callback)(const v8::WeakCallbackInfo<void>& data),
bool PassByPointer, ContainerType Type)
{
DataTransfer::SetPointer(MainIsolate, JSObject, Ptr, 0);
ContainerCacheItem& Val =
ContainerCache.Add(Ptr, {v8::UniquePersistent<v8::Value>(MainIsolate, JSObject), !PassByPointer, Type});
Val.Container.SetWeak<void>(nullptr, Callback, v8::WeakCallbackType::kInternalFields);
}
void FJsEnvImpl::UnBindContainer(void* Ptr)
{
ContainerCache.Remove(Ptr);
}
std::shared_ptr<FStructWrapper> FJsEnvImpl::GetStructWrapper(UStruct* InStruct, bool& IsReuseTemplate)
{
const auto FullName = InStruct->GetFullName();
auto TypeReflectionPtr = TypeReflectionMap.Find(FullName);
if (!TypeReflectionPtr)
{
auto Ret = std::make_shared<FStructWrapper>(InStruct);
TypeReflectionMap.Add(FullName, Ret);
// UE_LOG(LogTemp, Warning, TEXT("FJsEnvImpl::GetStructWrapper new %s // %s"), *InStruct->GetName(), *FullName);
return Ret;
}
else
{
#if PUERTS_REUSE_STRUCTWRAPPER_FUNCTIONTEMPLATE
IsReuseTemplate = true;
#endif
// UE_LOG(LogTemp, Warning, TEXT("FJsEnvImpl::GetStructWrapper existed %s // %s"), *InStruct->GetName(), *FullName);
(*TypeReflectionPtr)->Init(InStruct, IsReuseTemplate);
return (*TypeReflectionPtr);
}
}
FJsEnvImpl::FTemplateInfo* FJsEnvImpl::GetTemplateInfoOfType(UStruct* InStruct, bool& Existed)
{
auto Isolate = MainIsolate;
auto TemplateInfoPtr = TypeToTemplateInfoMap.Find(InStruct);
if (!TemplateInfoPtr)
{
if (!ExtensionMethodsMapInited)
{
InitExtensionMethodsMap();
}
v8::Local<v8::FunctionTemplate> Template;
bool IsReuseTemplate = false;
auto StructWrapper = GetStructWrapper(InStruct, IsReuseTemplate);
auto ExtensionMethodsIter = ExtensionMethodsMap.find(InStruct);
if (ExtensionMethodsIter != ExtensionMethodsMap.end())
{
StructWrapper->AddExtensionMethods(ExtensionMethodsIter->second);
ExtensionMethodsMap.erase(ExtensionMethodsIter);
}
if (auto ScriptStruct = Cast<UScriptStruct>(InStruct))
{
// Logger->Warn(FString::Printf(TEXT("UScriptStruct: %s"), *InStruct->GetName()));
Template = StructWrapper->ToFunctionTemplate(Isolate, FScriptStructWrapper::New);
if (!IsReuseTemplate)
{
#if WITH_EDITOR
Template->SetClassName(
v8::String::NewFromUtf8(Isolate, TCHAR_TO_UTF8(*InStruct->GetPathName()), v8::NewStringType::kNormal)
.ToLocalChecked());
#elif !defined(UE_SHIPPING)
Template->SetClassName(
v8::String::NewFromUtf8(Isolate, TCHAR_TO_UTF8(*InStruct->GetName()), v8::NewStringType::kNormal)
.ToLocalChecked());
#endif
}
if (!ScriptStruct->IsNative()) //非原生的结构体,可能在实例没有的时候会释放
{
SysObjectRetainer.Retain(ScriptStruct);
}
auto SuperStruct = ScriptStruct->GetSuperStruct();
if (SuperStruct)
{
bool Dummy;
if (IsReuseTemplate)
__USE(GetTemplateInfoOfType(SuperStruct, Dummy));
else
Template->Inherit(GetTemplateInfoOfType(SuperStruct, Dummy)->Template.Get(Isolate));
}
}
else
{
auto Class = Cast<UClass>(InStruct);
check(Class);
Template = StructWrapper->ToFunctionTemplate(Isolate, FClassWrapper::New);
if (!IsReuseTemplate)
{
#if WITH_EDITOR
Template->SetClassName(
v8::String::NewFromUtf8(Isolate, TCHAR_TO_UTF8(*InStruct->GetPathName()), v8::NewStringType::kNormal)
.ToLocalChecked());
#elif !defined(UE_SHIPPING)
Template->SetClassName(
v8::String::NewFromUtf8(Isolate, TCHAR_TO_UTF8(*InStruct->GetName()), v8::NewStringType::kNormal)
.ToLocalChecked());
#endif
}
#if PUERTS_KEEP_UOBJECT_REFERENCE
StructWrapper->IsNativeTakeJsRef = StructWrapper->IsTypeScriptGeneratedClass = IsTypeScriptGeneratedClass(Class);
#endif
auto SuperClass = Class->GetSuperClass();
if (SuperClass)
{
bool Dummy;
auto SuperTemplateInfo = GetTemplateInfoOfType(SuperClass, Dummy);
if (!IsReuseTemplate)
{
Template->Inherit(SuperTemplateInfo->Template.Get(Isolate));
}
if (SuperTemplateInfo->StructWrapper->IsNativeTakeJsRef)
{
StructWrapper->IsNativeTakeJsRef = true;
}
}
}
Existed = false;
return &TypeToTemplateInfoMap.Add(InStruct, {v8::UniquePersistent<v8::FunctionTemplate>(Isolate, Template), StructWrapper});
}
else
{
Existed = true;
return TemplateInfoPtr;
}
}
v8::Local<v8::Function> FJsEnvImpl::GetJsClass(UStruct* InStruct, v8::Local<v8::Context> Context)
{
bool Existed;
auto Ret = GetTemplateInfoOfType(InStruct, Existed)->Template.Get(MainIsolate)->GetFunction(Context).ToLocalChecked();
if (UNLIKELY(!Existed)) // first create
{
auto Class = Cast<UClass>(InStruct);
#if !defined(ENGINE_INDEPENDENT_JSENV)
if (Class && !Class->IsNative() && !InStruct->IsA<UTypeScriptGeneratedClass>())
{
auto SuperClass = Cast<UTypeScriptGeneratedClass>(Class->GetSuperClass());
if (SuperClass)
{
MakeSureInject(SuperClass, false, false);
v8::Local<v8::Value> VProto;
if (Ret->Get(Context, FV8Utils::ToV8String(MainIsolate, "prototype")).ToLocal(&VProto) && VProto->IsObject())
{
v8::Local<v8::Object> Proto = VProto.As<v8::Object>();
__USE(Proto->SetPrototype(Context, BindInfoMap[SuperClass].Prototype.Get(MainIsolate)));
}
}
}
#endif
}
return Ret;
}
bool FJsEnvImpl::IsInstanceOf(UStruct* Struct, v8::Local<v8::Object> JsObject)
{
//这里如果外面传一个非uobject或者ustructsrcipt的object,可能会有问题
if (Cast<UScriptStruct>(Struct))
{
UScriptStruct* ObjectStruct = (UScriptStruct*) FV8Utils::GetPointer(JsObject, 1);
return ObjectStruct && ObjectStruct->IsChildOf(Struct);
}
else
{
UObject* Object = FV8Utils::GetUObject(JsObject);
if (!Object || Object == RELEASED_UOBJECT)
{
return false;
}
return Object->GetClass()->IsChildOf(Struct);
}
}
bool FJsEnvImpl::IsInstanceOfCppObject(v8::Isolate* Isolate, const void* TypeId, v8::Local<v8::Object> JsObject)
{
return CppObjectMapper.IsInstanceOfCppObject(Isolate, TypeId, JsObject);
}
std::weak_ptr<int> FJsEnvImpl::GetJsEnvLifeCycleTracker()
{
return CppObjectMapper.GetJsEnvLifeCycleTracker();
}
v8::Local<v8::Value> FJsEnvImpl::AddSoftObjectPtr(
v8::Isolate* Isolate, v8::Local<v8::Context> Context, FSoftObjectPtr* SoftObjectPtr, UClass* Class, bool IsSoftClass)
{
const auto JSObject = SoftObjectPtrTemplate.Get(Isolate)->InstanceTemplate()->NewInstance(Context).ToLocalChecked();
DataTransfer::SetPointer(Isolate, JSObject, SoftObjectPtr, 0);
DataTransfer::SetPointer(Isolate, JSObject, Class, IsSoftClass ? 2 : 1);
DataTransfer::SetPointer(Isolate, JSObject, nullptr, IsSoftClass ? 1 : 2);
v8::Global<v8::Object>* GlobalPtr = new v8::Global<v8::Object>(Isolate, JSObject);
GlobalPtr->SetWeak<v8::Global<v8::Object>>(
GlobalPtr,
[](const v8::WeakCallbackInfo<v8::Global<v8::Object>>& Data)
{
void* Ptr = DataTransfer::MakeAddressWithHighPartOfTwo(Data.GetInternalField(0), Data.GetInternalField(1));
delete static_cast<FSoftObjectPtr*>(Ptr);
delete Data.GetParameter();
},
v8::WeakCallbackType::kInternalFields);
return JSObject;
}
v8::Local<v8::Value> FJsEnvImpl::UETypeToJsClass(v8::Isolate* Isolate, v8::Local<v8::Context> Context, UField* Type)
{
if (const auto Struct = Cast<UStruct>(Type))
{
return GetJsClass(Struct, Context);
}
if (const auto Enum = Cast<UEnum>(Type))
{
auto Result = v8::Object::New(Isolate);
for (int i = 0; i < Enum->NumEnums(); ++i)
{
#if !defined(ENGINE_INDEPENDENT_JSENV)
auto Name = Enum->IsA<UUserDefinedEnum>() ?
#if ENGINE_MINOR_VERSION >= 23 || ENGINE_MAJOR_VERSION > 4
Enum->GetAuthoredNameStringByIndex(i)
#else
Enum->GetDisplayNameTextByIndex(i).ToString()
#endif
: Enum->GetNameStringByIndex(i);
#else
auto Name = Enum->GetNameStringByIndex(i);
#endif
auto Value = Enum->GetValueByIndex(i);
__USE(Result->Set(Context, FV8Utils::ToV8String(Isolate, Name), v8::Number::New(Isolate, Value)));
__USE(Result->Set(Context, v8::Number::New(Isolate, Value), FV8Utils::ToV8String(Isolate, Name)));
}
__USE(Result->Set(
Context, FV8Utils::ToV8String(Isolate, "__puerts_ufield"), FindOrAdd(Isolate, Context, Enum->GetClass(), Enum)));
#if !defined(ENGINE_INDEPENDENT_JSENV)
if (Enum == StaticEnum<EObjectTypeQuery>())
{
UCollisionProfile* CollisionProfile = UCollisionProfile::Get();
int32 ContainerIndex = 0;
while (true)
{
FName ChannelName = CollisionProfile->ReturnChannelNameFromContainerIndex(ContainerIndex);
if (ChannelName == NAME_None)
{
break;
}
auto ObjectType = CollisionProfile->ConvertToObjectType((ECollisionChannel) ContainerIndex);
if (ObjectType != EObjectTypeQuery::ObjectTypeQuery_MAX)
{
__USE(Result->Set(Context, FV8Utils::ToV8String(Isolate, ChannelName), v8::Number::New(Isolate, ObjectType)));
__USE(Result->Set(Context, v8::Number::New(Isolate, ObjectType), FV8Utils::ToV8String(Isolate, ChannelName)));
}
ContainerIndex++;
}
}
else if (Enum == StaticEnum<ETraceTypeQuery>())
{
UCollisionProfile* CollisionProfile = UCollisionProfile::Get();
int32 ContainerIndex = 0;
while (true)
{
FName ChannelName = CollisionProfile->ReturnChannelNameFromContainerIndex(ContainerIndex);
if (ChannelName == NAME_None)
{
break;
}
auto TraceType = CollisionProfile->ConvertToTraceType((ECollisionChannel) ContainerIndex);
if (TraceType != ETraceTypeQuery::TraceTypeQuery_MAX)
{
__USE(Result->Set(Context, FV8Utils::ToV8String(Isolate, ChannelName), v8::Number::New(Isolate, TraceType)));
__USE(Result->Set(Context, v8::Number::New(Isolate, TraceType), FV8Utils::ToV8String(Isolate, ChannelName)));
}
ContainerIndex++;
}
}
#endif
return Result;
}
return v8::Undefined(Isolate);
}
void FJsEnvImpl::LoadUEType(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgString);
const FString TypeName = FV8Utils::ToFString(Isolate, Info[0]);
UField* Type = FindAnyType<UClass>(TypeName);
if (!Type)
{
Type = FindAnyType<UScriptStruct>(TypeName);
}
if (!Type)
{
Type = FindAnyType<UEnum>(TypeName);
}
if (!Type)
{
Type = LoadObject<UClass>(nullptr, *TypeName);
}
if (!Type)
{
Type = LoadObject<UScriptStruct>(nullptr, *TypeName);
}
if (!Type)
{
Type = LoadObject<UEnum>(nullptr, *TypeName);
}
if (Type && !Type->IsNative())
{
FV8Utils::ThrowException(Isolate,
FString::Printf(TEXT("%s is blueprint type, load it using UE.Class.Load('path/to/your/blueprint/file')."), *TypeName));
return;
}
auto Result = UETypeToJsClass(Isolate, Context, Type);
if (Result->IsUndefined())
{
FV8Utils::ThrowException(Isolate, FString::Printf(TEXT("can not find type:%s"), *TypeName));
}
else
{
Info.GetReturnValue().Set(Result);
}
}
void FJsEnvImpl::LoadCppType(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
CppObjectMapper.LoadCppType(Info);
}
void FJsEnvImpl::UEClassToJSClass(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgObject);
UField* Type = nullptr;
if (UObject* Object = FV8Utils::GetUObject(Context, Info[0]))
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
Type = Cast<UField>(Object);
}
if (Type)
{
Info.GetReturnValue().Set(UETypeToJsClass(Isolate, Context, Type));
}
else
{
FV8Utils::ThrowException(Isolate, FString::Printf(TEXT("argument #0 expect a UField")));
}
}
void FJsEnvImpl::SetJsTakeRefInTs(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
CHECK_V8_ARGS(EArgObject);
UObject* Object = FV8Utils::GetUObject(Context, Info[0]);
bool Existed;
auto TemplateInfoPtr = GetTemplateInfoOfType(Object->GetClass(), Existed);
SetJsTakeRef(Object, static_cast<FClassWrapper*>(TemplateInfoPtr->StructWrapper.get()));
}
bool FJsEnvImpl::GetContainerTypeProperty(v8::Local<v8::Context> Context, v8::Local<v8::Value> Value, PropertyMacro** PropertyPtr)
{
if (Value->IsInt32())
{
int Type = Value->Int32Value(Context).ToChecked();
if (Type >= MaxBuiltinType)
{
*PropertyPtr = nullptr;
return false;
}
*PropertyPtr = ContainerMeta.GetBuiltinProperty((BuiltinType) Type);
return true;
}
else if (auto Field = Cast<UField>(FV8Utils::GetUObject(Context, Value)))
{
*PropertyPtr = ContainerMeta.GetObjectProperty(Field);
return *PropertyPtr != nullptr;
}
else
{
*PropertyPtr = nullptr;
return false;
}
}
void FJsEnvImpl::NewContainer(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32);
int ContainerType = Info[0]->Int32Value(Context).ToChecked();
PropertyMacro* Property1 = nullptr;
PropertyMacro* Property2 = nullptr;
FScriptArray* ScriptArray = nullptr;
FScriptSet* ScriptSet = nullptr;
FScriptMap* ScriptMap = nullptr;
if (!GetContainerTypeProperty(Context, Info[1], &Property1))
{
FV8Utils::ThrowException(Isolate, FString::Printf(TEXT("can not get first type for %d"), ContainerType));
return;
}
switch (ContainerType)
{
case 0: // Array
ScriptArray = reinterpret_cast<FScriptArray*>(new FScriptArrayEx(Property1));
// Logger->Info(FString::Printf(TEXT("Array %s"), *Property1->GetClass()->GetName()));
Info.GetReturnValue().Set(FindOrAddContainer(Isolate, Context, Property1, ScriptArray, false));
break;
case 1: // Set
ScriptSet = reinterpret_cast<FScriptSet*>(new FScriptSetEx(Property1));
// Logger->Info(FString::Printf(TEXT("Set %s"), *Property1->GetClass()->GetName()));
Info.GetReturnValue().Set(FindOrAddContainer(Isolate, Context, Property1, ScriptSet, false));
break;
case 2: // Map
if (!GetContainerTypeProperty(Context, Info[2], &Property2))
{
FV8Utils::ThrowException(Isolate, FString::Printf(TEXT("can not get second type for %d"), ContainerType));
return;
}
// Logger->Info(FString::Printf(TEXT("Map %s %s"), *Property1->GetClass()->GetName(),
// *Property2->GetClass()->GetName()));
ScriptMap = reinterpret_cast<FScriptMap*>(new FScriptMapEx(Property1, Property2));
Info.GetReturnValue().Set(FindOrAddContainer(Isolate, Context, Property1, Property2, ScriptMap, false));
break;
default:
FV8Utils::ThrowException(Isolate, FString::Printf(TEXT("invalid container type %d"), ContainerType));
}
}
void FJsEnvImpl::Start(const FString& ModuleNameOrScript, const TArray<TPair<FString, UObject*>>& Arguments)
{
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
if (Started)
{
Logger->Error("Started yet!");
return;
}
auto Isolate = MainIsolate;
#ifdef THREAD_SAFE
v8::Locker Locker(Isolate);
#endif
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = v8::Local<v8::Context>::New(Isolate, DefaultContext);
v8::Context::Scope ContextScope(Context);
auto MaybeTGameTGJS = Context->Global()->Get(Context, FV8Utils::ToV8String(Isolate, "puerts"));
if (MaybeTGameTGJS.IsEmpty() || !MaybeTGameTGJS.ToLocalChecked()->IsObject())
{
Logger->Error("global.puerts not found!");
return;
}
auto TGJS = MaybeTGameTGJS.ToLocalChecked()->ToObject(Context).ToLocalChecked();
auto MaybeArgv = TGJS->Get(Context, FV8Utils::ToV8String(Isolate, "argv"));
if (MaybeArgv.IsEmpty() || !MaybeArgv.ToLocalChecked()->IsObject())
{
Logger->Error("global.puerts.argv not found!");
return;
}
auto Argv = MaybeArgv.ToLocalChecked()->ToObject(Context).ToLocalChecked();
auto MaybeArgvAdd = Argv->Get(Context, FV8Utils::ToV8String(Isolate, "add"));
if (MaybeArgvAdd.IsEmpty() || !MaybeArgvAdd.ToLocalChecked()->IsFunction())
{
Logger->Error("global.puerts.argv.add not found!");
return;
}
auto ArgvAdd = MaybeArgvAdd.ToLocalChecked().As<v8::Function>();
for (int i = 0; i < Arguments.Num(); i++)
{
auto Object = Arguments[i].Value;
v8::Local<v8::Value> Args[2] = {
FV8Utils::ToV8String(Isolate, Arguments[i].Key), FindOrAdd(Isolate, Context, Object->GetClass(), Object)};
(void) (ArgvAdd->Call(Context, Argv, 2, Args));
}
v8::TryCatch TryCatch(Isolate);
v8::Local<v8::Value> Args[] = {FV8Utils::ToV8String(Isolate, ModuleNameOrScript)};
__USE(Require.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 1, Args));
if (TryCatch.HasCaught())
{
Logger->Error(FV8Utils::TryCatchToString(Isolate, &TryCatch));
}
Started = true;
}
bool FJsEnvImpl::LoadFile(const FString& RequiringDir, const FString& ModuleName, FString& OutPath, FString& OutDebugPath,
TArray<uint8>& Data, FString& ErrInfo)
{
if (ModuleLoader->Search(RequiringDir, ModuleName, OutPath, OutDebugPath))
{
if (!ModuleLoader->Load(OutPath, Data))
{
ErrInfo = FString::Printf(TEXT("can not load [%s]"), *ModuleName);
return false;
}
}
else
{
ErrInfo = FString::Printf(TEXT("can not find [%s]"), *ModuleName);
return false;
}
return true;
}
#ifndef WITH_QUICKJS
std::unordered_multimap<int, FJsEnvImpl::FModuleInfo*>::iterator FJsEnvImpl::FindModuleInfo(v8::Local<v8::Module> Module)
{
auto Range = HashToModuleInfo.equal_range(Module->GetIdentityHash());
for (auto It = Range.first; It != Range.second; ++It)
{
if (It->second->Module == Module)
{
return It;
}
}
return HashToModuleInfo.end();
}
v8::MaybeLocal<v8::Module> FJsEnvImpl::ResolveModuleCallback(v8::Local<v8::Context> Context, v8::Local<v8::String> Specifier,
#if V8_MAJOR_VERSION >= 9
v8::Local<v8::FixedArray> ImportAttributes, // not implement yet
#endif
v8::Local<v8::Module> Referrer)
{
auto Self = static_cast<FJsEnvImpl*>(FV8Utils::IsolateData<IObjectMapper>(Context->GetIsolate()));
const auto ItModuleInfo = Self->FindModuleInfo(Referrer);
check(ItModuleInfo != Self->HashToModuleInfo.end());
const auto RefModuleName = FV8Utils::ToFString(Context->GetIsolate(), Specifier);
auto ItRefModule = ItModuleInfo->second->ResolveCache.Find(RefModuleName);
check(ItRefModule);
return (*ItRefModule).Get(Context->GetIsolate());
}
v8::MaybeLocal<v8::Module> FJsEnvImpl::FetchCJSModuleAsESModule(v8::Local<v8::Context> Context, const FString& ModuleName)
{
#if V8_MAJOR_VERSION < 8
FV8Utils::ThrowException(
MainIsolate, FString::Printf(TEXT("V8_MAJOR_VERSION < 8 not support fetch CJS module [%s] from ESM"), *ModuleName));
return v8::MaybeLocal<v8::Module>();
#else
const auto Isolate = Context->GetIsolate();
Logger->Info(FString::Printf(TEXT("ESM Fetch CJS Module: %s"), *ModuleName));
v8::Local<v8::Value> Args[] = {FV8Utils::ToV8String(Isolate, ModuleName)};
auto MaybeRet = Require.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 1, Args);
if (MaybeRet.IsEmpty())
{
return v8::MaybeLocal<v8::Module>();
}
auto CJSValue = MaybeRet.ToLocalChecked();
std::vector<v8::Local<v8::String>> ExportNames = {
v8::String::NewFromUtf8(Isolate, "default", v8::NewStringType::kNormal).ToLocalChecked()};
if (CJSValue->IsObject())
{
auto JsObject = CJSValue->ToObject(Context).ToLocalChecked();
auto Keys = JsObject->GetOwnPropertyNames(Context).ToLocalChecked();
for (decltype(Keys->Length()) i = 0; i < Keys->Length(); ++i)
{
v8::Local<v8::Value> Key;
if (Keys->Get(Context, i).ToLocal(&Key))
{
// UE_LOG(LogTemp, Warning, TEXT("---'%s' '%s'"), *ModuleName, *FV8Utils::ToFString(Isolate, Key));
ExportNames.push_back(Key->ToString(Context).ToLocalChecked());
}
}
}
v8::Local<v8::Module> SyntheticModule =
#if defined(V8_HAS_WRAP_API_WITHOUT_STL)
v8::Module_CreateSyntheticModule_Without_Stl(Isolate, FV8Utils::ToV8String(Isolate, ModuleName), ExportNames.data(),
ExportNames.size(),
#else
v8::Module::CreateSyntheticModule(Isolate, FV8Utils::ToV8String(Isolate, ModuleName), ExportNames,
#endif
[](v8::Local<v8::Context> ContextInner, v8::Local<v8::Module> Module) -> v8::MaybeLocal<v8::Value>
{
const auto IsolateInner = ContextInner->GetIsolate();
auto Self = static_cast<FJsEnvImpl*>(FV8Utils::IsolateData<IObjectMapper>(IsolateInner));
const auto ModuleInfoIt = Self->FindModuleInfo(Module);
check(ModuleInfoIt != Self->HashToModuleInfo.end());
auto CJSValueInner = ModuleInfoIt->second->CJSValue.Get(IsolateInner);
__USE(Module->SetSyntheticModuleExport(IsolateInner,
v8::String::NewFromUtf8(IsolateInner, "default", v8::NewStringType::kNormal).ToLocalChecked(), CJSValueInner));
if (CJSValueInner->IsObject())
{
auto JsObjectInner = CJSValueInner->ToObject(ContextInner).ToLocalChecked();
auto KeysInner = JsObjectInner->GetOwnPropertyNames(ContextInner).ToLocalChecked();
for (decltype(KeysInner->Length()) ii = 0; ii < KeysInner->Length(); ++ii)
{
v8::Local<v8::Value> KeyInner;
v8::Local<v8::Value> ValueInner;
if (KeysInner->Get(ContextInner, ii).ToLocal(&KeyInner) &&
JsObjectInner->Get(ContextInner, KeyInner).ToLocal(&ValueInner))
{
// UE_LOG(LogTemp, Warning, TEXT("-----set '%s'"), *FV8Utils::ToFString(IsolateInner, KeyInner));
__USE(Module->SetSyntheticModuleExport(
IsolateInner, KeyInner->ToString(ContextInner).ToLocalChecked(), ValueInner));
}
}
}
return v8::MaybeLocal<v8::Value>(v8::True(IsolateInner));
});
FModuleInfo* Info = new FModuleInfo;
Info->Module.Reset(Isolate, SyntheticModule);
Info->CJSValue.Reset(Isolate, CJSValue);
HashToModuleInfo.emplace(SyntheticModule->GetIdentityHash(), Info);
return SyntheticModule;
#endif
}
v8::MaybeLocal<v8::Module> FJsEnvImpl::FetchESModuleTree(v8::Local<v8::Context> Context, const FString& FileName)
{
const auto Isolate = Context->GetIsolate();
if (PathToModule.Contains(FileName))
{
return PathToModule[FileName].Get(Isolate);
}
Logger->Info(FString::Printf(TEXT("Fetch ES Module: %s"), *FileName));
TArray<uint8> Data;
if (!ModuleLoader->Load(FileName, Data))
{
FV8Utils::ThrowException(MainIsolate, FString::Printf(TEXT("can not load [%s]"), *FileName));
return v8::MaybeLocal<v8::Module>();
}
v8::Local<v8::String> Source;
v8::ScriptCompiler::CachedData* CachedCode = nullptr;
v8::ScriptCompiler::CompileOptions Options = v8::ScriptCompiler::CompileOptions::kNoCompileOptions;
#if defined(WITH_V8_BYTECODE)
if (FileName.EndsWith(TEXT(".mbc")))
{
FCodeCacheHeader* CodeCacheHeader = (FCodeCacheHeader*) Data.GetData();
if (CodeCacheHeader->FlagHash != Expect_FlagHash)
{
UE_LOG(Puerts, Warning, TEXT("FlagHash not match expect %u, but got %u"), Expect_FlagHash, CodeCacheHeader->FlagHash);
CodeCacheHeader->FlagHash = Expect_FlagHash;
}
#if V8_MAJOR_VERSION >= 11
if (CodeCacheHeader->ReadOnlySnapshotChecksum != Expect_ReadOnlySnapshotChecksum)
{
UE_LOG(Puerts, Warning, TEXT("ReadOnlySnapshotChecksum not match expect %u, but got %u"),
Expect_ReadOnlySnapshotChecksum, CodeCacheHeader->ReadOnlySnapshotChecksum);
CodeCacheHeader->ReadOnlySnapshotChecksum = Expect_ReadOnlySnapshotChecksum;
}
#endif
static constexpr uint32_t kModuleFlagMask = (1 << 31);
uint32_t Len = CodeCacheHeader->SourceHash & ~kModuleFlagMask;
v8::Local<v8::Value> Args[] = {v8::Integer::New(Isolate, Len)};
v8::Local<v8::Value> Ret;
if (!GenEmptyCode.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 1, Args).ToLocal(&Ret) || !Ret->IsString())
{
FV8Utils::ThrowException(MainIsolate, FString::Printf(TEXT("generate code for bytecode [%s] fail!"), *FileName));
return v8::MaybeLocal<v8::Module>();
}
CachedCode = new v8::ScriptCompiler::CachedData(Data.GetData(), Data.Num()); // will delete by ~Source
Options = v8::ScriptCompiler::CompileOptions::kConsumeCodeCache;
Source = Ret.As<v8::String>();
}
else
#endif
{
FString Script;
FFileHelper::BufferToString(Script, Data.GetData(), Data.Num());
Source = FV8Utils::ToV8String(Isolate, Script);
}
#if V8_MAJOR_VERSION > 8
v8::ScriptOrigin Origin(
Isolate, FV8Utils::ToV8String(Isolate, FileName), 0, 0, false, -1, v8::Local<v8::Value>(), false, false, true);
#else
v8::ScriptOrigin Origin(FV8Utils::ToV8String(Isolate, FileName), v8::Local<v8::Integer>(), v8::Local<v8::Integer>(),
v8::Local<v8::Boolean>(), v8::Local<v8::Integer>(), v8::Local<v8::Value>(), v8::Local<v8::Boolean>(),
v8::Local<v8::Boolean>(), v8::True(Isolate));
#endif
v8::ScriptCompiler::Source ScriptSource(Source, Origin, CachedCode);
v8::Local<v8::Module> Module;
if (!v8::ScriptCompiler::CompileModule(Isolate, &ScriptSource, Options).ToLocal(&Module))
{
return v8::MaybeLocal<v8::Module>();
}
PathToModule.Add(FileName, v8::Global<v8::Module>(Isolate, Module));
FModuleInfo* Info = new FModuleInfo;
Info->Module.Reset(Isolate, Module);
HashToModuleInfo.emplace(Module->GetIdentityHash(), Info);
auto DirName = FPaths::GetPath(FileName);
#if V8_MAJOR_VERSION >= 9
v8::Local<v8::FixedArray> module_requests = Module->GetModuleRequests();
for (int i = 0, Length = module_requests->Length(); i < Length; ++i)
{
v8::Local<v8::ModuleRequest> module_request = module_requests->Get(Context, i).As<v8::ModuleRequest>();
auto RefModuleName = FV8Utils::ToFString(Isolate, module_request->GetSpecifier());
#else
for (int i = 0, Length = Module->GetModuleRequestsLength(); i < Length; ++i)
{
auto RefModuleName = FV8Utils::ToFString(Isolate, Module->GetModuleRequest(i));
#endif
FString OutPath;
FString OutDebugPath;
if (ModuleLoader->Search(DirName, RefModuleName, OutPath, OutDebugPath))
{
if (OutPath.EndsWith(TEXT("package.json")))
{
TArray<uint8> PackageData;
if (ModuleLoader->Load(OutPath, PackageData))
{
FString PackageScript;
FFileHelper::BufferToString(PackageScript, PackageData.GetData(), PackageData.Num());
v8::Local<v8::Value> Args[] = {FV8Utils::ToV8String(Isolate, PackageScript)};
auto MaybeRet = GetESMMain.Get(Isolate)->Call(Context, v8::Undefined(Isolate), 1, Args);
v8::Local<v8::Value> ESMMainValue;
if (MaybeRet.ToLocal(&ESMMainValue) && ESMMainValue->IsString())
{
FString ESMMain = FV8Utils::ToFString(Isolate, ESMMainValue);
FString ESMMainOutPath;
FString ESMMainOutDebugPath;
if (ModuleLoader->Search(FPaths::GetPath(OutPath), ESMMain, ESMMainOutPath, ESMMainOutDebugPath))
{
OutPath = ESMMainOutPath;
}
}
}
}
if (OutPath.EndsWith(TEXT(".mjs")) || OutPath.EndsWith(TEXT(".js")))
{
auto RefModule = FetchESModuleTree(Context, OutPath);
if (RefModule.IsEmpty())
{
return v8::MaybeLocal<v8::Module>();
}
Info->ResolveCache.Add(RefModuleName, v8::Global<v8::Module>(Isolate, RefModule.ToLocalChecked()));
continue;
}
}
auto RefModule = FetchCJSModuleAsESModule(Context, OutPath.EndsWith(TEXT(".cjs")) ? OutPath : RefModuleName);
if (RefModule.IsEmpty())
{
FV8Utils::ThrowException(
MainIsolate, FString::Printf(TEXT("can not resolve [%s], import by [%s]"), *RefModuleName, *FileName));
return v8::MaybeLocal<v8::Module>();
}
Info->ResolveCache.Add(RefModuleName, v8::Global<v8::Module>(Isolate, RefModule.ToLocalChecked()));
}
return Module;
}
#endif
void FJsEnvImpl::ExecuteModule(const FString& ModuleName)
{
FString OutPath;
FString DebugPath;
TArray<uint8> Data;
FString ErrInfo;
if (!LoadFile(TEXT(""), ModuleName, OutPath, DebugPath, Data, ErrInfo))
{
Logger->Error(ErrInfo);
return;
}
// #if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
// if (!DebugPath.IsEmpty())
// OutPath = DebugPath;
// #endif
auto Isolate = MainIsolate;
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
auto Context = v8::Local<v8::Context>::New(Isolate, DefaultContext);
v8::Context::Scope ContextScope(Context);
#ifndef WITH_QUICKJS
if (OutPath.EndsWith(".mjs"))
{
v8::TryCatch TryCatch(Isolate);
v8::Local<v8::Module> RootModule;
if (!FetchESModuleTree(Context, OutPath).ToLocal(&RootModule))
{
check(TryCatch.HasCaught());
Logger->Error(FV8Utils::TryCatchToString(Isolate, &TryCatch));
return;
}
if (RootModule->InstantiateModule(Context, ResolveModuleCallback).FromMaybe(false))
{
__USE(RootModule->Evaluate(Context));
}
if (TryCatch.HasCaught())
{
Logger->Error(FV8Utils::TryCatchToString(Isolate, &TryCatch));
return;
}
}
else
#endif
{
v8::Local<v8::String> Source = FV8Utils::ToV8StringFromFileContent(Isolate, Data);
#if PLATFORM_WINDOWS
// 修改URL分隔符格式,否则无法匹配Inspector协议在打断点时发送的正则表达式,导致断点失败
FString FormattedScriptUrl = DebugPath.Replace(TEXT("/"), TEXT("\\"));
#else
FString FormattedScriptUrl = DebugPath;
#endif
v8::Local<v8::String> Name = FV8Utils::ToV8String(Isolate, FormattedScriptUrl);
#if V8_MAJOR_VERSION > 8
v8::ScriptOrigin Origin(Isolate, Name);
#else
v8::ScriptOrigin Origin(Name);
#endif
v8::TryCatch TryCatch(Isolate);
auto CompiledScript = v8::Script::Compile(Context, Source, &Origin);
if (CompiledScript.IsEmpty())
{
Logger->Error(FV8Utils::TryCatchToString(Isolate, &TryCatch));
return;
}
(void) (CompiledScript.ToLocalChecked()->Run(Context));
if (TryCatch.HasCaught())
{
Logger->Error(FV8Utils::TryCatchToString(Isolate, &TryCatch));
return;
}
}
}
void FJsEnvImpl::EvalScript(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
#ifndef WITH_QUICKJS
bool IsESM = Info[2]->BooleanValue(Isolate);
if (IsESM)
{
FString FullPath = FV8Utils::ToFString(Isolate, Info[3]);
v8::Local<v8::Module> RootModule;
if (!FetchESModuleTree(Context, FullPath).ToLocal(&RootModule))
{
return;
}
if (RootModule->InstantiateModule(Context, ResolveModuleCallback).FromMaybe(false))
{
auto MaybeResult = RootModule->Evaluate(Context);
v8::Local<v8::Value> Result;
if (MaybeResult.ToLocal(&Result))
{
if (Result->IsPromise())
{
v8::Local<v8::Promise> ResultPromise(Result.As<v8::Promise>());
while (ResultPromise->State() == v8::Promise::kPending)
{
Isolate->PerformMicrotaskCheckpoint();
}
if (ResultPromise->State() == v8::Promise::kRejected)
{
ResultPromise->MarkAsHandled();
Isolate->ThrowException(ResultPromise->Result());
return;
}
}
Info.GetReturnValue().Set(RootModule->GetModuleNamespace());
}
}
return;
}
#endif
v8::String::Utf8Value UrlArg(Isolate, Info[1]);
FString ScriptUrl = UTF8_TO_TCHAR(*UrlArg);
#if PLATFORM_WINDOWS
// 修改URL分隔符格式,否则无法匹配Inspector协议在打断点时发送的正则表达式,导致断点失败
FString FormattedScriptUrl = ScriptUrl.Replace(TEXT("/"), TEXT("\\"));
#else
FString FormattedScriptUrl = ScriptUrl;
#endif
v8::Local<v8::String> Name = FV8Utils::ToV8String(Isolate, FormattedScriptUrl);
#if V8_MAJOR_VERSION > 8
v8::ScriptOrigin Origin(Isolate, Name);
#else
v8::ScriptOrigin Origin(Name);
#endif
v8::Local<v8::String> Source = Info[0]->ToString(Context).ToLocalChecked();
#if defined(WITH_V8_BYTECODE)
v8::ScriptCompiler::CachedData* CachedCode = nullptr;
uint8_t* Cache = nullptr;
v8::ScriptCompiler::CompileOptions Options = v8::ScriptCompiler::CompileOptions::kNoCompileOptions;
if (Info.Length() > 4)
{
if (Info[4]->IsArrayBuffer())
{
auto AB = Info[4].As<v8::ArrayBuffer>();
auto Length = AB->ByteLength();
Cache = new uint8_t[Length];
memcpy(Cache, DataTransfer::GetArrayBufferData(AB), Length);
CachedCode = new v8::ScriptCompiler::CachedData(Cache, Length); // will delete by ~Source
Options = v8::ScriptCompiler::CompileOptions::kConsumeCodeCache;
FCodeCacheHeader* CodeCacheHeader = (FCodeCacheHeader*) CachedCode->data;
if (CodeCacheHeader->FlagHash != Expect_FlagHash)
{
UE_LOG(
Puerts, Warning, TEXT("FlagHash not match expect %u, but got %u"), Expect_FlagHash, CodeCacheHeader->FlagHash);
CodeCacheHeader->FlagHash = Expect_FlagHash;
}
}
}
v8::ScriptCompiler::Source ScriptSource(Source, Origin, CachedCode);
auto Script = v8::ScriptCompiler::Compile(Context, &ScriptSource, Options);
if (CachedCode)
{
delete Cache;
if (CachedCode->rejected)
{
FV8Utils::ThrowException(Isolate, TEXT("invalid bytecode"));
return;
}
}
#else
auto Script = v8::Script::Compile(Context, Source, &Origin);
#endif
if (Script.IsEmpty())
{
return;
}
auto Result = Script.ToLocalChecked()->Run(Context);
if (Result.IsEmpty())
{
return;
}
Info.GetReturnValue().Set(Result.ToLocalChecked());
if (OnSourceLoadedCallback)
{
OnSourceLoadedCallback(FormattedScriptUrl);
}
}
void FJsEnvImpl::Log(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32, EArgString);
auto Level = Info[0]->Int32Value(Context).ToChecked();
FString Msg = FV8Utils::ToFString(Isolate, Info[1]);
switch (Level)
{
case 1:
Logger->Info(Msg);
break;
case 2:
Logger->Warn(Msg);
break;
case 3:
Logger->Error(Msg);
break;
default:
Logger->Log(Msg);
break;
}
}
void FJsEnvImpl::SearchModule(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
CHECK_V8_ARGS(EArgString, EArgString);
FString ModuleName = FV8Utils::ToFString(Isolate, Info[0]);
FString RequiringDir = FV8Utils::ToFString(Isolate, Info[1]);
FString OutPath;
FString OutDebugPath;
if (ModuleLoader->Search(RequiringDir, ModuleName, OutPath, OutDebugPath))
{
auto Result = v8::Array::New(Isolate);
Result->Set(Context, 0, FV8Utils::ToV8String(Isolate, OutPath)).Check();
Result->Set(Context, 1, FV8Utils::ToV8String(Isolate, OutDebugPath)).Check();
Info.GetReturnValue().Set(Result);
}
}
void FJsEnvImpl::LoadModule(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgString);
FString Path = FV8Utils::ToFString(Isolate, Info[0]);
TArray<uint8> Data;
if (!ModuleLoader->Load(Path, Data))
{
FV8Utils::ThrowException(Isolate, "can not load module");
return;
}
#if defined(WITH_V8_BYTECODE)
if (Path.EndsWith(TEXT(".cbc")) || Path.EndsWith(TEXT(".mbc")))
{
v8::Local<v8::ArrayBuffer> Ab = v8::ArrayBuffer::New(Info.GetIsolate(), Data.Num());
void* Buff = DataTransfer::GetArrayBufferData(Ab);
::memcpy(Buff, Data.GetData(), Data.Num());
Info.GetReturnValue().Set(Ab);
}
else
#endif
{
Info.GetReturnValue().Set(FV8Utils::ToV8StringFromFileContent(Isolate, Data));
}
}
void FJsEnvImpl::SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
CHECK_V8_ARGS(EArgFunction, EArgNumber);
SetFTickerDelegate(Info, false);
}
void FJsEnvImpl::SetFTickerDelegate(const v8::FunctionCallbackInfo<v8::Value>& Info, bool Continue)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
while (!(++TimerID)) // TimerID > 0
{
}
uint32_t DelegateHandleId = TimerID;
FTimerInfo& TimerInfo = TimerInfos.Emplace(DelegateHandleId, FTimerInfo());
TimerInfo.Callback.Reset(Isolate, v8::Local<v8::Function>::Cast(Info[0]));
float Millisecond = Info[1]->NumberValue(Context).ToChecked();
float Delay = Millisecond / 1000.f;
TimerInfo.TickerHandle =
FUETicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this, DelegateHandleId, Continue](float)
{ return this->TimerCallback(DelegateHandleId, Continue); }),
Delay);
Info.GetReturnValue().Set(DelegateHandleId);
}
bool FJsEnvImpl::TimerCallback(int DelegateHandleId, bool Continue)
{
v8::Isolate* Isolate = MainIsolate;
#ifdef SINGLE_THREAD_VERIFY
ensureMsgf(BoundThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Access by illegal thread!"));
#endif
#ifdef THREAD_SAFE
v8::Locker Locker(MainIsolate);
#endif
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = DefaultContext.Get(Isolate);
v8::Context::Scope ContextScope(Context);
FTimerInfo* PTimeInfo = TimerInfos.Find(DelegateHandleId);
if (!PTimeInfo)
{
Logger->Warn(FString::Printf(TEXT("Try to callback a invalid timer: %d"), DelegateHandleId));
return false;
}
auto OriginHandle = PTimeInfo->TickerHandle;
v8::Local<v8::Function> Function = TimerInfos[DelegateHandleId].Callback.Get(Isolate);
v8::TryCatch TryCatch(Isolate);
(void) (Function->Call(Context, Context->Global(), 0, nullptr));
if (TryCatch.HasCaught())
{
FString Message =
FString::Printf(TEXT("Exception in Timer Callback: %s"), *(FV8Utils::TryCatchToString(Isolate, &TryCatch)));
Logger->Error(Message);
}
auto ClearInCallback = !TimerInfos.Contains(DelegateHandleId) || (OriginHandle != TimerInfos[DelegateHandleId].TickerHandle);
if (!Continue && !ClearInCallback)
{
RemoveFTickerDelegateHandle(DelegateHandleId);
}
return Continue && !ClearInCallback;
}
void FJsEnvImpl::RemoveFTickerDelegateHandle(int DelegateHandleId)
{
if (!TimerInfos.Contains(DelegateHandleId))
{
return;
}
FUETicker::GetCoreTicker().RemoveTicker(TimerInfos[DelegateHandleId].TickerHandle);
TimerInfos.Remove(DelegateHandleId);
}
void FJsEnvImpl::ClearInterval(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
// todo - mocha 7.0.1,当reporter为JSON,调用clearTimeout时,可能不传值,或传Null、Undefined过来。暂将其忽略
if (Info.Length() == 0)
{
Logger->Warn(TEXT("Calling ClearInterval with 0 argument."));
}
else if (Info[0]->IsNullOrUndefined())
{
// 屏蔽这条只在mocha中出现的警告
// Logger->Warn(TEXT("Calling ClearInterval with a Null or Undefined."));
}
else
{
CHECK_V8_ARGS(EArgInt32);
int HandleId = Info[0]->Int32Value(Context).ToChecked();
RemoveFTickerDelegateHandle(HandleId);
}
}
void FJsEnvImpl::SetInterval(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgFunction, EArgNumber);
SetFTickerDelegate(Info, true);
}
#if !defined(ENGINE_INDEPENDENT_JSENV)
void FJsEnvImpl::MakeUClass(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgFunction, EArgObject, EArgString, EArgObject, EArgObject);
auto Constructor = v8::Local<v8::Function>::Cast(Info[0]);
auto Prototype = Info[1]->ToObject(Context).ToLocalChecked();
auto ClassName = FV8Utils::ToFString(Isolate, Info[2]);
auto Methods = Info[3]->ToObject(Context).ToLocalChecked();
UClass* ParentUClass = nullptr;
if (UObject* Object = FV8Utils::GetUObject(Context, Info[4]))
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
ParentUClass = Cast<UClass>(Object);
}
if (!ParentUClass)
{
FV8Utils::ThrowException(Isolate, "#4 parameter expect a UClass object");
return;
}
FString GenClassName;
int i = 0;
while (true)
{
GenClassName = FString::Printf(TEXT("%s%d"), *ClassName, i);
if (!FindAnyType<UClass>(GenClassName))
break;
i++;
}
auto Class = UJSGeneratedClass::Create(GenClassName, ParentUClass, DynamicInvoker, Isolate, Constructor, Prototype);
TSet<FName> overrided;
for (TFieldIterator<UFunction> It(ParentUClass, EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated,
EFieldIteratorFlags::IncludeInterfaces);
It; ++It)
{
UFunction* Function = *It;
auto FunctionFName = Function->GetFName();
if (!overrided.Contains(FunctionFName) && Function->HasAnyFunctionFlags(FUNC_BlueprintEvent))
{
auto MaybeValue = Methods->Get(Context, FV8Utils::ToV8String(Isolate, Function->GetName()));
if (!MaybeValue.IsEmpty() && MaybeValue.ToLocalChecked()->IsFunction())
{
// Logger->Warn(FString::Printf(TEXT("override: %s"), *Function->GetName()));
UJSGeneratedClass::Override(
Isolate, Class, Function, v8::Local<v8::Function>::Cast(MaybeValue.ToLocalChecked()), DynamicInvoker, true);
overrided.Add(FunctionFName);
}
}
}
Class->Bind();
Class->StaticLink(true);
// Make sure CDO is ready for use
(void) (Class->GetDefaultObject());
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 12
// Assemble reference token stream for garbage collection/ RTGC.
if (!Class->HasAnyClassFlags(CLASS_TokenStreamAssembled))
{
Class->AssembleReferenceTokenStream();
}
#endif
if (!GeneratedClasses.Contains(Class))
{
GeneratedClasses.Add(Class);
}
SysObjectRetainer.Retain(Class);
auto Result = FindOrAdd(Isolate, Context, Class->GetClass(), Class);
Info.GetReturnValue().Set(Result);
}
void FJsEnvImpl::Mixin(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
CHECK_V8_ARGS(EArgObject, EArgObject);
UClass* To = nullptr;
if (UObject* Object = FV8Utils::GetUObject(Context, Info[0]))
{
if (FV8Utils::IsReleasedPtr(Object))
{
FV8Utils::ThrowException(Isolate, "passing a invalid object");
return;
}
To = Cast<UClass>(Object);
}
if (!To)
{
FV8Utils::ThrowException(Isolate, "#0 parameter expect a Blueprint UClass");
return;
}
// release
if (Info[5]->IsBoolean() && Info[5]->BooleanValue(Isolate))
{
MixinClasses.Remove(To);
UJSGeneratedClass::Restore(To);
return;
}
if (MixinClasses.Contains(To))
{
FV8Utils::ThrowException(Isolate, "had mixin");
return;
}
auto MixinMethods = Info[1]->ToObject(Context).ToLocalChecked();
bool TakeJsObjectRef = false;
if (Info[2]->IsBoolean())
{
TakeJsObjectRef = Info[2]->BooleanValue(Isolate);
}
bool Inherit = false;
if (Info[3]->IsBoolean())
{
Inherit = Info[3]->BooleanValue(Isolate);
}
bool NoWarning = false;
if (Info[4]->IsBoolean())
{
NoWarning = Info[4]->BooleanValue(Isolate);
}
UClass* New = To;
if (Inherit)
{
New = NewObject<UClass>(To->GetOuter(), To->GetClass(),
MakeUniqueObjectName(To->GetOuter(), To->GetClass(), *(To->GetName() + TEXT("_MixinGen_"))));
New->PropertyLink = To->PropertyLink;
New->ClassWithin = To->ClassWithin;
New->ClassConfigName = To->ClassConfigName;
New->ClassFlags = To->ClassFlags;
New->ClassCastFlags = To->ClassCastFlags;
New->ClassConstructor = To->ClassConstructor;
New->ClassFlags = New->ClassFlags | EClassFlags::CLASS_Transient;
New->ClassFlags = New->ClassFlags & (~EClassFlags::CLASS_Intrinsic);
New->SetFlags(EObjectFlags::RF_Transient);
New->SetSuperStruct(To);
}
auto Keys = MixinMethods->GetOwnPropertyNames(Context).ToLocalChecked();
TArray<FName> ReplaceMethodNames;
for (decltype(Keys->Length()) i = 0; i < Keys->Length(); ++i)
{
auto Key = Keys->Get(Context, i).ToLocalChecked();
auto MethodName = FV8Utils::ToFName(Isolate, Key);
auto Function = To->FindFunctionByName(MethodName);
if (Function)
{
auto JsFunc = MixinMethods->Get(Context, Key).ToLocalChecked();
auto MixinedFunc = UJSGeneratedClass::Mixin(Isolate, New, Function, MixinInvoker, TakeJsObjectRef, !NoWarning);
MixinFunctionMap.Emplace(
MixinedFunc, v8::UniquePersistent<v8::Function>(Isolate, v8::Local<v8::Function>::Cast(JsFunc)));
ReplaceMethodNames.Add(MethodName);
}
}
if (Inherit)
{
New->Bind();
New->StaticLink(true);
(void) (New->GetDefaultObject());
if (auto AnimClass = Cast<UAnimBlueprintGeneratedClass>(New))
{
AnimClass->UpdateCustomPropertyListForPostConstruction();
}
else if (auto WidgetClass = Cast<UWidgetBlueprintGeneratedClass>(New))
{
WidgetClass->UpdateCustomPropertyListForPostConstruction();
}
else if (auto BPClass = Cast<UBlueprintGeneratedClass>(New))
{
BPClass->UpdateCustomPropertyListForPostConstruction();
}
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 12
// 拷贝创建的Class需要手动重新创建ReferenceTokenStream
New->AssembleReferenceTokenStream(true);
#endif
}
else
{
To->ClearFunctionMapsCaches();
bool IsReuseTemplate = false;
auto StructWrapper = GetStructWrapper(To, IsReuseTemplate);
for (int i = 0; i < ReplaceMethodNames.Num(); i++)
{
StructWrapper->RefreshMethod(To->FindFunctionByName(ReplaceMethodNames[i]));
}
}
MixinClasses.Add(New);
bool Existed = false;
__USE(GetTemplateInfoOfType(New, Existed));
bool IsReuseTemplate = false;
auto StructWrapper = GetStructWrapper(New, IsReuseTemplate);
StructWrapper->IsNativeTakeJsRef = TakeJsObjectRef;
for (auto& KV : TypeToTemplateInfoMap)
{
if (New != KV.Key && KV.Key->IsChildOf(New))
{
KV.Value.StructWrapper->IsNativeTakeJsRef = TakeJsObjectRef;
}
}
Info.GetReturnValue().Set(FindOrAdd(Isolate, Context, New->GetClass(), New));
}
#endif
void FJsEnvImpl::FindModule(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgString);
std::string Name = *(v8::String::Utf8Value(Isolate, Info[0]));
auto Func = FindAddonRegisterFunc(Name);
if (Func)
{
auto Exports = v8::Object::New(Isolate);
Func(Context, Exports);
Info.GetReturnValue().Set(Exports);
}
}
void FJsEnvImpl::SetInspectorCallback(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
#ifndef WITH_QUICKJS
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
if (!Inspector)
return;
CHECK_V8_ARGS(EArgFunction);
if (!InspectorChannel)
{
InspectorChannel = Inspector->CreateV8InspectorChannel();
InspectorChannel->OnMessage(
[this](std::string Message)
{
// UE_LOG(LogTemp, Warning, TEXT("<-- %s"), UTF8_TO_TCHAR(Message.c_str()));
v8::Isolate::Scope IsolatescopeObject(MainIsolate);
v8::HandleScope HandleScopeObject(MainIsolate);
v8::Local<v8::Context> ContextInner = DefaultContext.Get(MainIsolate);
v8::Context::Scope ContextScopeObject(ContextInner);
auto Handler = InspectorMessageHandler.Get(MainIsolate);
v8::Local<v8::Value> Args[] = {FV8Utils::ToV8String(MainIsolate, Message.c_str())};
v8::TryCatch TryCatch(MainIsolate);
__USE(Handler->Call(ContextInner, ContextInner->Global(), 1, Args));
if (TryCatch.HasCaught())
{
Logger->Error(FString::Printf(
TEXT("inspector callback exception %s"), *FV8Utils::TryCatchToString(MainIsolate, &TryCatch)));
}
});
}
InspectorMessageHandler.Reset(Isolate, v8::Local<v8::Function>::Cast(Info[0]));
#endif // !WITH_QUICKJS
}
void FJsEnvImpl::DispatchProtocolMessage(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
#ifndef WITH_QUICKJS
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope Isolatescope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgString);
if (InspectorChannel)
{
FString Message = FV8Utils::ToFString(Isolate, Info[0]);
// UE_LOG(LogTemp, Warning, TEXT("--> %s"), *Message);
InspectorChannel->DispatchProtocolMessage(TCHAR_TO_UTF8(*Message));
}
#endif // !WITH_QUICKJS
}
void FJsEnvImpl::DumpStatisticsLog(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
#ifndef WITH_QUICKJS
v8::HeapStatistics Statistics;
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
Isolate->GetHeapStatistics(&Statistics);
FString StatisticsLog = FString::Printf(TEXT("------------------------\n"
"Dump Statistics of V8:\n"
"total_heap_size: %llu\n"
"total_heap_size_executable: %llu\n"
"total_physical_size: %llu\n"
"total_available_size: %llu\n"
"used_heap_size: %llu\n"
"heap_size_limit: %llu\n"
"malloced_memory: %llu\n"
"external_memory: %llu\n"
"peak_malloced_memory: %llu\n"
"number_of_native_contexts: %llu\n"
"number_of_detached_contexts: %llu\n"
"does_zap_garbage: %llu\n"
"------------------------\n"),
Statistics.total_heap_size(), Statistics.total_heap_size_executable(), Statistics.total_physical_size(),
Statistics.total_available_size(), Statistics.used_heap_size(), Statistics.heap_size_limit(), Statistics.malloced_memory(),
Statistics.external_memory(), Statistics.peak_malloced_memory(), Statistics.number_of_native_contexts(),
Statistics.number_of_detached_contexts(), Statistics.does_zap_garbage());
Logger->Info(StatisticsLog);
#endif // !WITH_QUICKJS
}
#if USE_WASM3
void FJsEnvImpl::Wasm_NewMemory(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32, EArgInt32);
int InitPages = Info[0]->Int32Value(Context).ToChecked();
int MaxPages = Info[1]->Int32Value(Context).ToChecked();
check(InitPages >= 0 && MaxPages > 0 && MaxPages >= InitPages);
auto Runtime = std::make_shared<WasmRuntime>(PuertsWasmEnv.get(), MaxPages, InitPages);
PuertsWasmRuntimeList.Add(Runtime);
Info.GetReturnValue().Set(Runtime->GetRuntimeSeq());
}
void FJsEnvImpl::Wasm_MemoryGrowth(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32, EArgInt32);
int Seq = Info[0]->Int32Value(Context).ToChecked();
int n = Info[1]->Int32Value(Context).ToChecked();
for (auto Runtime : PuertsWasmRuntimeList)
{
if (Runtime->GetRuntimeSeq() == Seq)
{
int previous = Runtime->Grow(n);
Info.GetReturnValue().Set(previous);
return;
}
}
FV8Utils::ThrowException(Isolate, "can not find associated runtime with memory");
return;
}
void FJsEnvImpl::Wasm_MemoryBuffer(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32);
int Seq = Info[0]->Int32Value(Context).ToChecked();
for (auto Runtime : PuertsWasmRuntimeList)
{
if (Runtime->GetRuntimeSeq() == Seq)
{
int Length = 0;
uint8* Ptr = Runtime->GetBuffer(Length);
if (Ptr)
{
auto Buffer = DataTransfer::NewArrayBuffer(Context, Ptr, Length);
Info.GetReturnValue().Set(Buffer);
}
else
{
FV8Utils::ThrowException(Isolate, "no memory?");
}
return;
}
}
FV8Utils::ThrowException(Isolate, "can not find associated runtime with memory");
return;
}
void FJsEnvImpl::Wasm_TableGrowth(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32, EArgInt32, EArgInt32);
int Seq = Info[0]->Int32Value(Context).ToChecked();
int Index = Info[1]->Int32Value(Context).ToChecked();
int N = Info[2]->Int32Value(Context).ToChecked();
for (auto Runtime : PuertsWasmRuntimeList)
{
if (Runtime->GetRuntimeSeq() == Seq)
{
if (auto ModuleInstance = Runtime->GetModuleInstance(Index))
{
Info.GetReturnValue().Set(static_cast<uint32_t>(ModuleInstance->TableGrow(N)));
}
else
{
FV8Utils::ThrowException(Isolate, "invalid Module Instance index");
}
return;
}
}
FV8Utils::ThrowException(Isolate, "can not find associated runtime with memory");
}
void FJsEnvImpl::Wasm_TableSet(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32, EArgInt32, EArgInt32, EArgFunction);
int Seq = Info[0]->Int32Value(Context).ToChecked();
int Index = Info[1]->Int32Value(Context).ToChecked();
int Pos = Info[2]->Int32Value(Context).ToChecked();
auto Func = v8::Local<v8::Function>::Cast(Info[3]);
v8::Local<v8::Value> FuncData;
if (!Func->Get(Context, FV8Utils::ToV8String(Isolate, M3_FUNCTION_KEY)).ToLocal(&FuncData) || !FuncData->IsExternal())
{
FV8Utils::ThrowException(Isolate, "Argument 1 must be null or a WebAssembly function of type compatible to 'this'");
return;
}
for (auto Runtime : PuertsWasmRuntimeList)
{
if (Runtime->GetRuntimeSeq() == Seq)
{
if (auto ModuleInstance = Runtime->GetModuleInstance(Index))
{
ModuleInstance->TableSet(Pos, static_cast<IM3Function>(v8::Local<v8::External>::Cast(FuncData)->Value()));
}
else
{
FV8Utils::ThrowException(Isolate, "invalid Module Instance index");
}
return;
}
}
FV8Utils::ThrowException(Isolate, "can not find associated runtime with memory");
}
void FJsEnvImpl::Wasm_TableLen(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
CHECK_V8_ARGS(EArgInt32, EArgInt32);
int Seq = Info[0]->Int32Value(Context).ToChecked();
int Index = Info[1]->Int32Value(Context).ToChecked();
for (auto Runtime : PuertsWasmRuntimeList)
{
if (Runtime->GetRuntimeSeq() == Seq)
{
if (auto ModuleInstance = Runtime->GetModuleInstance(Index))
{
Info.GetReturnValue().Set(static_cast<uint32_t>(ModuleInstance->TableLen()));
}
else
{
FV8Utils::ThrowException(Isolate, "invalid Module Instance index");
}
return;
}
}
FV8Utils::ThrowException(Isolate, "can not find associated runtime with memory");
}
void FJsEnvImpl::Wasm_Instance(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
v8::Isolate* Isolate = Info.GetIsolate();
v8::Isolate::Scope IsolateScope(Isolate);
v8::HandleScope HandleScope(Isolate);
v8::Local<v8::Context> Context = Isolate->GetCurrentContext();
v8::Context::Scope ContextScope(Context);
// typedarray or arraybuffer, importobject or undefined, exports object
if (Info.Length() != 3)
{
FV8Utils::ThrowException(Isolate, "params number dismatch");
return;
}
if (!Info[2]->IsObject())
{
FV8Utils::ThrowException(Isolate, "params at 3 must be object");
return;
}
v8::Local<v8::Object> ExportsObject = Info[2].As<v8::Object>();
v8::Local<v8::ArrayBuffer> InArrayBuffer;
if (Info[0]->IsArrayBuffer())
{
InArrayBuffer = Info[0].As<v8::ArrayBuffer>();
}
else if (Info[0]->IsTypedArray())
{
InArrayBuffer = Info[0].As<v8::TypedArray>()->Buffer();
}
void* Buffer = static_cast<char*>(DataTransfer::GetArrayBufferData(InArrayBuffer));
int BufferLength = InArrayBuffer->ByteLength();
TArray<uint8> InData;
InData.Append((uint8*) Buffer, BufferLength);
auto Runtime = NormalInstanceModule(
Isolate, Context, InData, ExportsObject, Info[1], PuertsWasmRuntimeList, PuertsWasmCachedLinkFunctionList);
if (Runtime)
{
Info.GetReturnValue().Set(Runtime->GetRuntimeSeq());
}
}
void FJsEnvImpl::Wasm_OverrideWebAssembly(const v8::FunctionCallbackInfo<v8::Value>& Info)
{
#if WASM3_OVERRIDE_WEBASSEMBLY
Info.GetReturnValue().Set(v8::True(Info.GetIsolate()));
#else
Info.GetReturnValue().Set(v8::False(Info.GetIsolate()));
#endif
}
#endif
} // namespace PUERTS_NAMESPACE