/* * 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. */ #pragma once #include "JsEnv.h" #include "DynamicDelegateProxy.h" #include "StructWrapper.h" #include "CppObjectMapper.h" #include "V8Utils.h" #include "ObjectMapper.h" #include "JSLogger.h" #include "ObjectRetainer.h" #if !defined(ENGINE_INDEPENDENT_JSENV) #include "TypeScriptGeneratedClass.h" #endif #include "UECompatible.h" #include "ContainerMeta.h" #include "ObjectCacheNode.h" #include #if ENGINE_MINOR_VERSION >= 25 || ENGINE_MAJOR_VERSION > 4 #include "UObject/WeakFieldPtr.h" #endif PRAGMA_DISABLE_UNDEFINED_IDENTIFIER_WARNINGS #pragma warning(push, 0) #include "libplatform/libplatform.h" #include "v8.h" #pragma warning(pop) PRAGMA_ENABLE_UNDEFINED_IDENTIFIER_WARNINGS #include "NamespaceDef.h" #include "V8InspectorImpl.h" #if defined(WITH_NODEJS) PRAGMA_DISABLE_UNDEFINED_IDENTIFIER_WARNINGS #pragma warning(push, 0) #include "node.h" #include "uv.h" #pragma warning(pop) PRAGMA_ENABLE_UNDEFINED_IDENTIFIER_WARNINGS #endif #if USE_WASM3 #include "WasmRuntime.h" #include "PuertsWasm/WasmJsFunctionParams.h" #endif #if V8_MAJOR_VERSION < 8 || defined(WITH_QUICKJS) || defined(WITH_NODEJS) || (WITH_EDITOR && !defined(FORCE_USE_STATIC_V8_LIB)) #define WITH_BACKING_STORE_AUTO_FREE 0 #else #define WITH_BACKING_STORE_AUTO_FREE 1 #endif namespace PUERTS_NAMESPACE { class JSError { public: FString Message; JSError() { } explicit JSError(const FString& m) : Message(m) { } }; class FJsEnvImpl : public IJsEnv, IObjectMapper, public FUObjectArray::FUObjectDeleteListener { public: explicit FJsEnvImpl(const FString& ScriptRoot); FJsEnvImpl(std::shared_ptr InModuleLoader, std::shared_ptr InLogger, int InPort, std::function InOnSourceLoadedCallback, const FString InFlags, void* InExternalRuntime, void* InExternalContext); virtual ~FJsEnvImpl() override; virtual void Start(const FString& ModuleNameOrScript, const TArray>& Arguments) override; virtual bool IdleNotificationDeadline(double DeadlineInSeconds) override; virtual void LowMemoryNotification() override; virtual void RequestMinorGarbageCollectionForTesting() override; virtual void RequestFullGarbageCollectionForTesting() override; virtual void WaitDebugger(double timeout) override { #ifdef THREAD_SAFE v8::Locker Locker(MainIsolate); #endif const auto startTime = FDateTime::Now(); while (Inspector && !Inspector->Tick()) { if (timeout > 0) { auto now = FDateTime::Now(); if ((now - startTime).GetTotalSeconds() >= timeout) { break; } } } } #if !defined(ENGINE_INDEPENDENT_JSENV) virtual void TryBindJs(const class UObjectBase* InObject) override; virtual void RebindJs() override; #endif virtual FString CurrentStackTrace() override; virtual void InitExtensionMethodsMap() override; void JsHotReload(FName ModuleName, const FString& JsSource); virtual void ReloadModule(FName ModuleName, const FString& JsSource) override; virtual void ReloadSource(const FString& Path, const std::string& JsSource) override; std::function OnSourceLoadedCallback; virtual void OnSourceLoaded(std::function Callback) override; public: bool IsTypeScriptGeneratedClass(UClass* Class); void SetJsTakeRef(UObject* UEObject, FClassWrapper* ClassWrapper); virtual void Bind(FClassWrapper* ClassWrapper, UObject* UEObject, v8::Local JSObject) override; virtual void UnBind(UClass* Class, UObject* UEObject) override; virtual void UnBind(UClass* Class, UObject* UEObject, bool ResetPointer); v8::Local FindOrAdd( v8::Isolate* InIsolate, v8::Local& Context, UClass* Class, UObject* UEObject, bool SkipTypeScriptInitial); virtual v8::Local FindOrAdd( v8::Isolate* InIsolate, v8::Local& Context, UClass* Class, UObject* UEObject) override; virtual void BindStruct( FScriptStructWrapper* ScriptStructWrapper, void* Ptr, v8::Local JSObject, bool PassByPointer) override; virtual void UnBindStruct(FScriptStructWrapper* ScriptStructWrapper, void* Ptr) override; virtual void UnBindCppObject(v8::Isolate* Isolate, JSClassDefinition* ClassDefinition, void* Ptr) override; virtual v8::Local FindOrAddStruct( v8::Isolate* Isolate, v8::Local& Context, UScriptStruct* ScriptStruct, void* Ptr, bool PassByPointer) override; virtual void BindCppObject(v8::Isolate* InIsolate, JSClassDefinition* ClassDefinition, void* Ptr, v8::Local JSObject, bool PassByPointer) override; virtual void* GetPrivateData(v8::Local Context, v8::Local JSObject) override; virtual void SetPrivateData(v8::Local Context, v8::Local JSObject, void* Ptr) override; virtual v8::MaybeLocal LoadTypeById(v8::Local Context, const void* TypeId) override; virtual v8::Local FindOrAddCppObject( v8::Isolate* Isolate, v8::Local& Context, const void* TypeId, void* Ptr, bool PassByPointer) override; virtual void Merge( v8::Isolate* Isolate, v8::Local Context, v8::Local Src, UStruct* DesType, void* Des) override; enum ContainerType { EArray, EMap, ESet }; void BindContainer(void* Ptr, v8::Local JSObject, void (*Callback)(const v8::WeakCallbackInfo& data), bool PassByPointer, ContainerType Type); virtual void UnBindContainer(void* Ptr) override; virtual v8::Local FindOrAddContainer(v8::Isolate* Isolate, v8::Local& Context, PropertyMacro* Property, FScriptArray* Ptr, bool PassByPointer) override; virtual v8::Local FindOrAddContainer(v8::Isolate* Isolate, v8::Local& Context, PropertyMacro* Property, FScriptSet* Ptr, bool PassByPointer) override; virtual v8::Local FindOrAddContainer(v8::Isolate* Isolate, v8::Local& Context, PropertyMacro* KeyProperty, PropertyMacro* ValueProperty, FScriptMap* Ptr, bool PassByPointer) override; virtual v8::Local FindOrAddDelegate(v8::Isolate* Isolate, v8::Local& Context, UObject* Owner, PropertyMacro* Property, void* DelegatePtr, bool PassByPointer) override; virtual bool AddToDelegate( v8::Isolate* Isolate, v8::Local& Context, void* DelegatePtr, v8::Local JsFunction) override; virtual PropertyMacro* FindDelegateProperty(void* DelegatePtr) override; virtual FScriptDelegate NewDelegate(v8::Isolate* Isolate, v8::Local& Context, UObject* Owner, v8::Local JsFunction, UFunction* SignatureFunction) override; void ReleaseManualReleaseDelegate(const v8::FunctionCallbackInfo& Info); virtual bool RemoveFromDelegate( v8::Isolate* Isolate, v8::Local& Context, void* DelegatePtr, v8::Local JsFunction) override; virtual bool ClearDelegate(v8::Isolate* Isolate, v8::Local& Context, void* DelegatePtr) override; virtual void ExecuteDelegate(v8::Isolate* Isolate, v8::Local& Context, const v8::FunctionCallbackInfo& Info, void* DelegatePtr) override; virtual bool IsInstanceOf(UStruct* Struct, v8::Local JsObject) override; virtual bool IsInstanceOfCppObject(v8::Isolate* Isolate, const void* TypeId, v8::Local JsObject) override; virtual std::weak_ptr GetJsEnvLifeCycleTracker() override; virtual v8::Local AddSoftObjectPtr(v8::Isolate* Isolate, v8::Local Context, FSoftObjectPtr* SoftObjectPtr, UClass* Class, bool IsSoftClass) override; bool CheckDelegateProxies(float Tick); virtual v8::Local CreateArray( v8::Isolate* Isolate, v8::Local& Context, FPropertyTranslator* Property, void* ArrayPtr) override; void InvokeDelegateCallback(UDynamicDelegateProxy* Proxy, void* Params); #if !defined(ENGINE_INDEPENDENT_JSENV) void JsConstruct(UClass* Class, UObject* Object, const v8::UniquePersistent& Constructor, const v8::UniquePersistent& Prototype); void TsConstruct(UTypeScriptGeneratedClass* Class, UObject* Object); void InvokeJsMethod(UObject* ContextObject, UJSGeneratedFunction* Function, FFrame& Stack, void* RESULT_PARAM); void InvokeMixinMethod(UObject* ContextObject, UJSGeneratedFunction* Function, FFrame& Stack, void* RESULT_PARAM); void TypeScriptInitial(UClass* Class, UObject* Object, const bool TypeScriptClassFound = false); void InvokeTsMethod(UObject* ContextObject, UFunction* Function, FFrame& Stack, void* RESULT_PARAM); void NotifyReBind(UTypeScriptGeneratedClass* Class); #endif v8::UniquePersistent JsPromiseRejectCallback; V8_INLINE static FJsEnvImpl* Get(v8::Isolate* Isolate) { return static_cast(FV8Utils::IsolateData(Isolate)); } public: #if ENGINE_MINOR_VERSION > 22 || ENGINE_MAJOR_VERSION > 4 virtual void OnUObjectArrayShutdown() override { GUObjectArray.RemoveUObjectDeleteListener(static_cast(this)); } #endif virtual void NotifyUObjectDeleted(const class UObjectBase* Object, int32 Index) override; void TryReleaseType(UStruct* Struct); private: bool LoadFile(const FString& RequiringDir, const FString& ModuleName, FString& OutPath, FString& OutDebugPath, TArray& Data, FString& ErrInfo); void ExecuteModule(const FString& ModuleName); void EvalScript(const v8::FunctionCallbackInfo& Info); void Log(const v8::FunctionCallbackInfo& Info); void SearchModule(const v8::FunctionCallbackInfo& Info); void LoadModule(const v8::FunctionCallbackInfo& Info); v8::Local UETypeToJsClass(v8::Isolate* Isolate, v8::Local Context, UField* Type); void LoadUEType(const v8::FunctionCallbackInfo& Info); void LoadCppType(const v8::FunctionCallbackInfo& Info); void UEClassToJSClass(const v8::FunctionCallbackInfo& Info); void SetJsTakeRefInTs(const v8::FunctionCallbackInfo& Info); bool GetContainerTypeProperty(v8::Local Context, v8::Local Value, PropertyMacro** PropertyPtr); void NewContainer(const v8::FunctionCallbackInfo& Info); std::shared_ptr GetStructWrapper(UStruct* InStruct, bool& IsReuseTemplate); struct FTemplateInfo { v8::UniquePersistent Template; std::shared_ptr StructWrapper; }; FTemplateInfo* GetTemplateInfoOfType(UStruct* Class, bool& Existed); v8::Local GetJsClass(UStruct* Class, v8::Local Context); FPropertyTranslator* GetContainerPropertyTranslator(PropertyMacro* Property); void SetTimeout(const v8::FunctionCallbackInfo& Info); void SetFTickerDelegate(const v8::FunctionCallbackInfo& Info, bool Continue); bool TimerCallback(int DelegateHandleId, bool Continue); void RemoveFTickerDelegateHandle(int HandleId); void SetInterval(const v8::FunctionCallbackInfo& Info); void ClearInterval(const v8::FunctionCallbackInfo& Info); void MergeObject(const v8::FunctionCallbackInfo& Info); void NewObjectByClass(const v8::FunctionCallbackInfo& Info); void NewStructByScriptStruct(const v8::FunctionCallbackInfo& Info); #if !defined(ENGINE_INDEPENDENT_JSENV) void MakeUClass(const v8::FunctionCallbackInfo& Info); TArray> MixinClasses; void Mixin(const v8::FunctionCallbackInfo& Info); #endif void FindModule(const v8::FunctionCallbackInfo& Info); void DumpStatisticsLog(const v8::FunctionCallbackInfo& Info); void SetInspectorCallback(const v8::FunctionCallbackInfo& Info); void DispatchProtocolMessage(const v8::FunctionCallbackInfo& Info); #ifndef WITH_QUICKJS v8::MaybeLocal FetchESModuleTree(v8::Local Context, const FString& FileName); v8::MaybeLocal FetchCJSModuleAsESModule(v8::Local Context, const FString& ModuleName); struct FModuleInfo { v8::Global Module; TMap> ResolveCache; v8::Global CJSValue; }; std::unordered_multimap::iterator FindModuleInfo(v8::Local Module); static v8::MaybeLocal ResolveModuleCallback(v8::Local Context, v8::Local Specifier, #if V8_MAJOR_VERSION >= 9 v8::Local ImportAttributes, // not implement yet #endif v8::Local Referrer); #endif struct ObjectMerger; std::unique_ptr& GetObjectMerger(UStruct* Struct); struct ObjectMerger { std::map> Fields; UStruct* Struct; FJsEnvImpl* Parent; ObjectMerger(FJsEnvImpl* InParent, UStruct* InStruct) { Parent = InParent; Struct = InStruct; for (TFieldIterator It(Struct); It; ++It) { PropertyMacro* Property = *It; TStringConversion> Name(*Property->GetName()); Fields[Name.Get()] = FPropertyTranslator::Create(Property); } } void Merge(v8::Isolate* Isolate, v8::Local Context, v8::Local JsObject, void* Ptr) { if (auto Class = Cast(Struct)) { UObject* Object = reinterpret_cast(Ptr); if (!Object->IsValidLowLevel() || UEObjectIsPendingKill(Object) || Object->GetClass() != Class || FV8Utils::GetPointer(JsObject)) { return; } } auto Keys = JsObject->GetOwnPropertyNames(Context).ToLocalChecked(); for (decltype(Keys->Length()) i = 0; i < Keys->Length(); ++i) { auto Key = Keys->Get(Context, i).ToLocalChecked(); auto Iter = Fields.find(*v8::String::Utf8Value(Isolate, Key)); if (Iter != Fields.end()) { auto MaybeValue = JsObject->Get(Context, Key); if (!MaybeValue.IsEmpty()) { auto Value = MaybeValue.ToLocalChecked(); if (Value->IsObject()) { auto JsObjectField = Value->ToObject(Context).ToLocalChecked(); if (!FV8Utils::GetPointerFast(JsObjectField)) { UStruct* FieldStruct = nullptr; if (auto ObjectPropertyBase = CastFieldMacro(Iter->second->Property)) { FieldStruct = ObjectPropertyBase->PropertyClass; } else if (auto StructProperty = CastFieldMacro(Iter->second->Property)) { FieldStruct = StructProperty->Struct; } if (FieldStruct) { Parent->GetObjectMerger(FieldStruct) ->Merge(Isolate, Context, JsObjectField, Iter->second->Property->ContainerPtrToValuePtr(Ptr)); } continue; } } if (!Value->IsUndefined()) Iter->second->JsToUEInContainer(Isolate, Context, Value, Ptr, true); } } } } }; friend ObjectMerger; #if USE_WASM3 std::shared_ptr PuertsWasmEnv; //在执行module.instance的时候,如果有指定memory,那么这个module对应会创建一个runtime TArray> PuertsWasmRuntimeList; TArray PuertsWasmCachedLinkFunctionList; protected: void Wasm_NewMemory(const v8::FunctionCallbackInfo& Info); void Wasm_MemoryGrowth(const v8::FunctionCallbackInfo& Info); void Wasm_MemoryBuffer(const v8::FunctionCallbackInfo& Info); void Wasm_TableGrowth(const v8::FunctionCallbackInfo& Info); void Wasm_TableSet(const v8::FunctionCallbackInfo& Info); void Wasm_TableLen(const v8::FunctionCallbackInfo& Info); void Wasm_Instance(const v8::FunctionCallbackInfo& Info); void Wasm_OverrideWebAssembly(const v8::FunctionCallbackInfo& Info); #endif public: #if !defined(ENGINE_INDEPENDENT_JSENV) class TsDynamicInvokerImpl : public ITsDynamicInvoker { public: TsDynamicInvokerImpl(FJsEnvImpl* InParent) : Parent(InParent) { } virtual void TsConstruct(UTypeScriptGeneratedClass* Class, UObject* Object) override { if (Parent) Parent->TsConstruct(Class, Object); } virtual void InvokeTsMethod(UObject* ContextObject, UFunction* Function, FFrame& Stack, void* RESULT_PARAM) override { if (Parent) Parent->InvokeTsMethod(ContextObject, Function, Stack, RESULT_PARAM); } virtual void NotifyReBind(UTypeScriptGeneratedClass* Class) override { if (Parent) Parent->NotifyReBind(Class); } FJsEnvImpl* Parent; }; TSharedPtr TsDynamicInvoker; TSharedPtr MixinInvoker; #endif private: FObjectRetainer UserObjectRetainer; FObjectRetainer SysObjectRetainer; std::shared_ptr ModuleLoader; std::shared_ptr Logger; bool Started; private: v8::Isolate::CreateParams CreateParams; #if defined(WITH_NODEJS) uv_loop_t NodeUVLoop; std::unique_ptr NodeArrayBufferAllocator; node::IsolateData* NodeIsolateData; node::Environment* NodeEnv; uv_thread_t PollingThread; uv_sem_t PollingSem; uv_async_t DummyUVHandle; bool PollingClosed = false; FGraphEventRef LastJob; #if PLATFORM_LINUX int Epoll; #endif void StartPolling(); void UvRunOnce(); void PollEvents(); static void OnWatcherQueueChanged(uv_loop_t* loop); void WakeupPollingThread(); void StopPolling(); #endif v8::Isolate* MainIsolate; v8::Global DefaultContext; v8::Global Require; v8::Global GetESMMain; v8::Global ReloadJs; #if !PUERTS_FORCE_CPP_UFUNCTION v8::Global MergePrototype; #endif v8::Global RemoveListItem; v8::Global GenListApply; #if defined(WITH_V8_BYTECODE) v8::Global GenEmptyCode; #endif TMap TypeToTemplateInfoMap; TMap> TypeReflectionMap; TMap> ObjectMap; TMap StructCache; struct ContainerCacheItem { v8::UniquePersistent Container; bool NeedRelease; ContainerType Type; }; TMap ContainerCache; FCppObjectMapper CppObjectMapper; v8::UniquePersistent ArrayTemplate; v8::UniquePersistent SetTemplate; v8::UniquePersistent MapTemplate; v8::UniquePersistent FixSizeArrayTemplate; struct ContainerPropertyInfo { #if ENGINE_MINOR_VERSION < 25 && ENGINE_MAJOR_VERSION < 5 TWeakObjectPtr PropertyWeakPtr; #else TWeakFieldPtr PropertyWeakPtr; #endif std::unique_ptr PropertyTranslator; }; std::map ContainerPropertyMap; std::map> JsCallbackPrototypeMap; std::map> ObjectMergers; struct DelegateObjectInfo { v8::UniquePersistent JSObject; // function to proxy save here TWeakObjectPtr Owner; //可用于自动清理 DelegatePropertyMacro* DelegateProperty; MulticastDelegatePropertyMacro* MulticastDelegateProperty; UFunction* SignatureFunction; bool PassByPointer; TWeakObjectPtr Proxy; v8::UniquePersistent JsCallbacks; }; struct TsFunctionInfo { v8::UniquePersistent JsFunction; std::unique_ptr FunctionTranslator; }; class DynamicInvokerImpl : public IDynamicInvoker { public: DynamicInvokerImpl(FJsEnvImpl* InParent) : Parent(InParent) { } virtual void InvokeDelegateCallback(UDynamicDelegateProxy* Proxy, void* Params) override { if (Parent) Parent->InvokeDelegateCallback(Proxy, Params); } #if !defined(ENGINE_INDEPENDENT_JSENV) virtual void JsConstruct(UClass* Class, UObject* Object, const v8::UniquePersistent& Constructor, const v8::UniquePersistent& Prototype) override { if (Parent) Parent->JsConstruct(Class, Object, Constructor, Prototype); } virtual void InvokeJsMethod( UObject* ContextObject, UJSGeneratedFunction* Function, FFrame& Stack, void* RESULT_PARAM) override { if (Parent) Parent->InvokeJsMethod(ContextObject, Function, Stack, RESULT_PARAM); } virtual void InvokeMixinMethod( UObject* ContextObject, UJSGeneratedFunction* Function, FFrame& Stack, void* RESULT_PARAM) override { if (Parent) Parent->InvokeMixinMethod(ContextObject, Function, Stack, RESULT_PARAM); } #endif FJsEnvImpl* Parent; }; #if !defined(ENGINE_INDEPENDENT_JSENV) struct FBindInfo { FName Name; v8::UniquePersistent Constructor; v8::UniquePersistent Prototype; bool InjectNotFinished; }; TMap BindInfoMap; void FinishInjection(UClass* InClass); void MakeSureInject(UTypeScriptGeneratedClass* Class, bool ForceReinject, bool RebindObject); #endif TSharedPtr DynamicInvoker; TSet GeneratedClasses; v8::UniquePersistent DelegateTemplate; v8::UniquePersistent MulticastDelegateTemplate; v8::UniquePersistent SoftObjectPtrTemplate; std::map DelegateMap; TMap TsFunctionMap; TMap> MixinFunctionMap; std::map> ExtensionMethodsMap; bool ExtensionMethodsMapInited = false; struct FTimerInfo { v8::Global Callback; FUETickDelegateHandle TickerHandle; }; uint32_t TimerID = 0; TMap TimerInfos; FUETickDelegateHandle DelegateProxiesCheckerHandler; V8Inspector* Inspector; V8InspectorChannel* InspectorChannel; v8::Global InspectorMessageHandler; FContainerMeta ContainerMeta; v8::Global ManualReleaseCallbackMap; std::vector> ManualReleaseCallbackList; TMap>> AutoReleaseCallbacksMap; #ifndef WITH_QUICKJS TMap> PathToModule; std::unordered_multimap HashToModuleInfo; #endif #ifdef SINGLE_THREAD_VERIFY uint32 BoundThreadId; #endif typedef void (FJsEnvImpl::*V8MethodCallback)(const v8::FunctionCallbackInfo& Info); template struct MethodBindingHelper { static void Bind(v8::Isolate* Isolate, v8::Local Context, v8::Local Obj, const char* Key, v8::Local This) { Obj->Set(Context, FV8Utils::ToV8String(Isolate, Key), v8::FunctionTemplate::New( Isolate, [](const v8::FunctionCallbackInfo& Info) { auto Self = static_cast((v8::Local::Cast(Info.Data()))->Value()); (Self->*callback)(Info); }, This) ->GetFunction(Context) .ToLocalChecked()) .Check(); } }; #if defined(WITH_V8_BYTECODE) uint32_t Expect_FlagHash = 0; #if V8_MAJOR_VERSION >= 11 uint32_t Expect_ReadOnlySnapshotChecksum = 0; #endif #endif }; } // namespace PUERTS_NAMESPACE