/* * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "BaseObjectJS.h" #include #include #include #include // this class is used to store a reference to a JS object in the metaobject. class JSWrapperState : public CORE_NS::IInterface { public: static constexpr BASE_NS::Uid UID { "2ef39765-91f2-46c4-b85f-7cad40dd3bcd" }; const IInterface* GetInterface(const BASE_NS::Uid& uid) const override; IInterface* GetInterface(const BASE_NS::Uid& uid) override; void Ref() override; void Unref() override; JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name); ~JSWrapperState(); NapiApi::Object GetObject(); private: static void Call(napi_env env, napi_value js_callback, void* context, void* inData); static void Final(napi_env env, void* finalize_data, void* finalize_hint); BASE_NS::string name_; volatile int32_t count_ { 0 }; napi_env env_ { nullptr }; napi_ref ref_ { nullptr }; napi_threadsafe_function termfun { nullptr }; struct fun_parm { napi_ref ref { nullptr }; napi_threadsafe_function termfun { nullptr }; }; }; const CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const { if (uid == CORE_NS::IInterface::UID) { return this; } if (uid == JSWrapperState::UID) { return this; } return nullptr; } CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) { if (uid == CORE_NS::IInterface::UID) { return this; } if (uid == JSWrapperState::UID) { return this; } return nullptr; } void JSWrapperState::Ref() { CORE_NS::AtomicIncrement(&count_); } void JSWrapperState::Unref() { if (CORE_NS::AtomicDecrement(&count_) == 0) { delete this; } } JSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name) : name_(name), env_(obj.GetEnv()) { napi_status status; // Create a WEAK reference to the object status = napi_create_reference(env_, obj.ToNapiValue(), 0, &ref_); // Create a thread safe function to release the js reference, as the destructor MAY be called from engine thread. napi_value jname = nullptr; status = napi_create_string_latin1(env_, "JSW", NAPI_AUTO_LENGTH, &jname); status = napi_create_threadsafe_function( env_, nullptr, nullptr, jname, 0, 1, nullptr, &JSWrapperState::Final, nullptr, &JSWrapperState::Call, &termfun); } void JSWrapperState::Call(napi_env env, napi_value js_callback, void* context, void* inData) { napi_status status; fun_parm* ref = (fun_parm*)inData; status = napi_delete_reference(env, ref->ref); status = napi_release_threadsafe_function(ref->termfun, napi_threadsafe_function_release_mode::napi_tsfn_release); delete ref; } void JSWrapperState::Final(napi_env env, void* finalize_data, void* context) {}; JSWrapperState::~JSWrapperState() { napi_status status; // trigger the threadsafe function, so that the javascript "weak reference" will be destroyed. status = napi_call_threadsafe_function( termfun, new fun_parm { ref_, termfun }, napi_threadsafe_function_call_mode::napi_tsfn_blocking); } NapiApi::Object JSWrapperState::GetObject() { napi_status status; napi_value value; status = napi_get_reference_value(env_, ref_, &value); return { env_, value }; } TrueRootObject::TrueRootObject() {} void TrueRootObject::destroy(TrueRootObject* object) { delete object; } bool TrueRootObject::IsStrong() const { return obj_ != nullptr; } void TrueRootObject::SetNativeObject(META_NS::IObject::Ptr real, bool strong) { if (strong) { obj_ = real; } else { objW_ = real; } } META_NS::IObject::Ptr TrueRootObject::GetNativeObject() { // if we have a strong ref... if (obj_) { // return that directly. return obj_; } // otherwise try to lock the weak reference. return objW_.lock(); } void TrueRootObject::Finalize(napi_env env) { // Synchronously destroy the lume object in engine thread.. (only for strong refs.) if (obj_) { ExecSyncTask([obj = BASE_NS::move(obj_)]() { return META_NS::IAny::Ptr {}; }); } // and reset the weak ref too. (which may be null anyway) objW_.reset(); } NapiApi::Function GetJSConstructor(napi_env env, const BASE_NS::string_view jsName) { NapiApi::MyInstanceState* mis; GetInstanceData(env, (void**)&mis); return NapiApi::Function(env, mis->FetchCtor(jsName)); } NapiApi::Object CreateJsObj(napi_env env, const BASE_NS::string_view jsName, META_NS::IObject::Ptr real, bool strong, uint32_t argc, napi_value* argv) { NapiApi::Object obj(GetJSConstructor(env, jsName), argc, argv); if (!obj) { return {}; } auto oo = GetRootObject(obj); oo->SetNativeObject(real, strong); return obj; } bool IsInstanceOf(const NapiApi::Object& obj, const BASE_NS::string_view jsName) { auto env = obj.GetEnv(); auto cl = GetJSConstructor(env, jsName); bool result = false; auto status = napi_instanceof(env, obj.ToNapiValue(), cl, &result); return result; } NapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj, BASE_NS::string_view name) { using namespace META_NS; // access hidden property. if (auto AppMeta = interface_pointer_cast(obj)) { if (auto wrapper = AppMeta->GetProperty(name, MetadataQuery::EXISTING)) { // The native object already contains a JS object. return interface_cast(wrapper->GetValue())->GetObject(); } } return {}; } NapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, NapiApi::Object jsobj, BASE_NS::string_view name) { using namespace META_NS; if (auto AppMeta = interface_pointer_cast(obj)) { // Add a reference to the JS object to the native object. const auto& obr = GetObjectRegistry(); auto wrapper = AppMeta->GetProperty(name, META_NS::MetadataQuery::EXISTING); // check if the property exists. if (wrapper) { // .. does the wrapper exist? (ie. reference from native to js) if (auto val = interface_cast(wrapper->GetValue())) { // validate it auto ref = val->GetObject(); bool equals = false; if (ref) { // we have ref.. so make the compare here napi_strict_equals(jsobj.GetEnv(), jsobj.ToNapiValue(), ref.ToNapiValue(), &equals); if (!equals) { // this may be a problem // (there should only be a 1-1 mapping between js objects and native objects) LOG_F("_JSW exists and points to different object!"); } else { // objects already linked return ref; } } else { // creating a new jsobject for existing native object that was bound before (but js object was GC:d) // this is fine. LOG_V( "Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)"); } } } else { // create the wrapper property wrapper = ConstructProperty( name, nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE); AppMeta->AddProperty(wrapper); } // link native to js. auto res = BASE_NS::shared_ptr(new JSWrapperState(jsobj, obj->GetClassName())); wrapper->SetValue(res); return interface_cast(res)->GetObject(); } napi_value val; napi_get_null(jsobj.GetEnv(), &val); return { jsobj.GetEnv(), val }; } BASE_NS::string GetConstructorName(const META_NS::IObject::Ptr& obj) { BASE_NS::string name { obj->GetClassName() }; // specialize/remap class names & interfaces. if (name == "Bitmap") { name = "Image"; } else if (name == "Tonemap") { name = "ToneMappingSettings"; } else if (name == "PostProcess") { name = "PostProcessSettings"; } else if (name == "Material") { // ok } else if (name == "Shader") { // possible specialization? } else if (name == "EcsAnimation") { name = "Animation"; } else if (name == "MeshNode") { name = "Geometry"; } else if (name == "CameraNode") { name = "Camera"; } else if (name == "LightNode") { SCENE_NS::ILight* lgt = interface_cast(obj); if (lgt == nullptr) { LOG_E("lgt is null"); return name; } auto type = lgt->Type()->GetValue(); if (type == Scene::LightType::DIRECTIONAL) { name = "DirectionalLight"; } else if (type == Scene::LightType::POINT) { name = "PointLight"; } else if (type == Scene::LightType::SPOT) { name = "SpotLight"; } else { name = "Node"; } } else if (name.ends_with("Node")) { name = "Node"; } return name; } NapiApi::Object CreateFromNativeInstance(napi_env env, const META_NS::IObject::Ptr& obj, bool strong, uint32_t argc, napi_value* argv, BASE_NS::string_view pname) { napi_value null; napi_get_null(env, &null); if (obj == nullptr) { return {}; } using namespace META_NS; NapiApi::Object nodeJS = FetchJsObj(obj, pname); if (nodeJS) { // we have a cached js object for this native object return nodeJS; } // no js object. create it. auto name = GetConstructorName(obj); MakeNativeObjectParam(env, obj, argc, argv); nodeJS = CreateJsObj(env, name, obj, strong, argc, argv); if (!nodeJS) { // EEK. could not create the object. LOG_E("Could not create JSObject for Class %s", name.c_str()); return {}; } return StoreJsObj(obj, nodeJS, pname); } void DebugNativesHavingJS(void) { for (auto&& v : META_NS::GetObjectRegistry().GetAllObjectInstances()) { if (auto i = interface_cast(v)) { auto p = i->GetProperty("_JSW", META_NS::MetadataQuery::EXISTING); if (!p) { p = i->GetProperty("_JSWMesh", META_NS::MetadataQuery::EXISTING); } if (p) { LOG_W("obj: %s (%p, strong count %u)", v->GetName().c_str(), v.get(), v.use_count()); if (auto val = interface_cast(p->GetValue())) { if (auto ref = val->GetObject()) { auto* tro = ref.Native(); if (tro) { if (auto m = interface_pointer_cast(tro->GetNativeObject())) { LOG_W("still has valid object: %s (%p, strong count %u) strong: %d (tro: %p)", m->GetName().c_str(), m.get(), m.use_count(), tro->IsStrong(), tro); } } } } } } } }