18bf80f4bSopenharmony_ci/*
28bf80f4bSopenharmony_ci * Copyright (C) 2024 Huawei Device Co., Ltd.
38bf80f4bSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
48bf80f4bSopenharmony_ci * you may not use this file except in compliance with the License.
58bf80f4bSopenharmony_ci * You may obtain a copy of the License at
68bf80f4bSopenharmony_ci *
78bf80f4bSopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
88bf80f4bSopenharmony_ci *
98bf80f4bSopenharmony_ci * Unless required by applicable law or agreed to in writing, software
108bf80f4bSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
118bf80f4bSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
128bf80f4bSopenharmony_ci * See the License for the specific language governing permissions and
138bf80f4bSopenharmony_ci * limitations under the License.
148bf80f4bSopenharmony_ci */
158bf80f4bSopenharmony_ci#include "BaseObjectJS.h"
168bf80f4bSopenharmony_ci
178bf80f4bSopenharmony_ci#include <meta/interface/intf_metadata.h>
188bf80f4bSopenharmony_ci#include <meta/interface/property/construct_property.h>
198bf80f4bSopenharmony_ci#include <scene_plugin/interface/intf_mesh.h>
208bf80f4bSopenharmony_ci#include <scene_plugin/interface/intf_nodes.h>
218bf80f4bSopenharmony_ci
228bf80f4bSopenharmony_ci// this class is used to store a reference to a JS object in the metaobject.
238bf80f4bSopenharmony_ciclass JSWrapperState : public CORE_NS::IInterface {
248bf80f4bSopenharmony_cipublic:
258bf80f4bSopenharmony_ci    static constexpr BASE_NS::Uid UID { "2ef39765-91f2-46c4-b85f-7cad40dd3bcd" };
268bf80f4bSopenharmony_ci    const IInterface* GetInterface(const BASE_NS::Uid& uid) const override;
278bf80f4bSopenharmony_ci    IInterface* GetInterface(const BASE_NS::Uid& uid) override;
288bf80f4bSopenharmony_ci    void Ref() override;
298bf80f4bSopenharmony_ci    void Unref() override;
308bf80f4bSopenharmony_ci    JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name);
318bf80f4bSopenharmony_ci    ~JSWrapperState();
328bf80f4bSopenharmony_ci    NapiApi::Object GetObject();
338bf80f4bSopenharmony_ci
348bf80f4bSopenharmony_ciprivate:
358bf80f4bSopenharmony_ci    BASE_NS::string name_;
368bf80f4bSopenharmony_ci    volatile int32_t count_ { 0 };
378bf80f4bSopenharmony_ci    napi_env env_;
388bf80f4bSopenharmony_ci    napi_ref ref_;
398bf80f4bSopenharmony_ci};
408bf80f4bSopenharmony_ci
418bf80f4bSopenharmony_ciconst CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const
428bf80f4bSopenharmony_ci{
438bf80f4bSopenharmony_ci    if (uid == CORE_NS::IInterface::UID) {
448bf80f4bSopenharmony_ci        return this;
458bf80f4bSopenharmony_ci    }
468bf80f4bSopenharmony_ci    if (uid == JSWrapperState::UID) {
478bf80f4bSopenharmony_ci        return this;
488bf80f4bSopenharmony_ci    }
498bf80f4bSopenharmony_ci    return nullptr;
508bf80f4bSopenharmony_ci}
518bf80f4bSopenharmony_ciCORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid)
528bf80f4bSopenharmony_ci{
538bf80f4bSopenharmony_ci    if (uid == CORE_NS::IInterface::UID) {
548bf80f4bSopenharmony_ci        return this;
558bf80f4bSopenharmony_ci    }
568bf80f4bSopenharmony_ci    if (uid == JSWrapperState::UID) {
578bf80f4bSopenharmony_ci        return this;
588bf80f4bSopenharmony_ci    }
598bf80f4bSopenharmony_ci    return nullptr;
608bf80f4bSopenharmony_ci}
618bf80f4bSopenharmony_civoid JSWrapperState::Ref()
628bf80f4bSopenharmony_ci{
638bf80f4bSopenharmony_ci    CORE_NS::AtomicIncrement(&count_);
648bf80f4bSopenharmony_ci}
658bf80f4bSopenharmony_civoid JSWrapperState::Unref()
668bf80f4bSopenharmony_ci{
678bf80f4bSopenharmony_ci    if (CORE_NS::AtomicDecrement(&count_) == 0) {
688bf80f4bSopenharmony_ci        delete this;
698bf80f4bSopenharmony_ci    }
708bf80f4bSopenharmony_ci}
718bf80f4bSopenharmony_ciJSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name)
728bf80f4bSopenharmony_ci{
738bf80f4bSopenharmony_ci    name_ = name;
748bf80f4bSopenharmony_ci    LOG_F("JSWrapperState ++ %s", name_.c_str());
758bf80f4bSopenharmony_ci    env_ = obj.GetEnv();
768bf80f4bSopenharmony_ci    // Create a WEAK reference to the object
778bf80f4bSopenharmony_ci    napi_create_reference(env_, obj, 0, &ref_);
788bf80f4bSopenharmony_ci}
798bf80f4bSopenharmony_ciJSWrapperState::~JSWrapperState()
808bf80f4bSopenharmony_ci{
818bf80f4bSopenharmony_ci    // release the reference.
828bf80f4bSopenharmony_ci
838bf80f4bSopenharmony_ci    napi_delete_reference(env_, ref_);
848bf80f4bSopenharmony_ci    LOG_F("JSWrapperState -- %s", name_.c_str());
858bf80f4bSopenharmony_ci}
868bf80f4bSopenharmony_ciNapiApi::Object JSWrapperState::GetObject()
878bf80f4bSopenharmony_ci{
888bf80f4bSopenharmony_ci    napi_value value;
898bf80f4bSopenharmony_ci    napi_get_reference_value(env_, ref_, &value);
908bf80f4bSopenharmony_ci    return { env_, value };
918bf80f4bSopenharmony_ci}
928bf80f4bSopenharmony_ci
938bf80f4bSopenharmony_ciTrueRootObject::TrueRootObject() {}
948bf80f4bSopenharmony_civoid TrueRootObject::SetNativeObject(META_NS::IObject::Ptr real, bool strong)
958bf80f4bSopenharmony_ci{
968bf80f4bSopenharmony_ci    if (strong) {
978bf80f4bSopenharmony_ci        obj_ = real;
988bf80f4bSopenharmony_ci    } else {
998bf80f4bSopenharmony_ci        objW_ = real;
1008bf80f4bSopenharmony_ci    }
1018bf80f4bSopenharmony_ci}
1028bf80f4bSopenharmony_ciMETA_NS::IObject::Ptr TrueRootObject::GetNativeObject()
1038bf80f4bSopenharmony_ci{
1048bf80f4bSopenharmony_ci    // if we have a strong ref...
1058bf80f4bSopenharmony_ci    if (obj_) {
1068bf80f4bSopenharmony_ci        // return that directly.
1078bf80f4bSopenharmony_ci        return obj_;
1088bf80f4bSopenharmony_ci    }
1098bf80f4bSopenharmony_ci    // otherwise try to lock the weak reference.
1108bf80f4bSopenharmony_ci    return objW_.lock();
1118bf80f4bSopenharmony_ci}
1128bf80f4bSopenharmony_civoid TrueRootObject::Finalize(napi_env env)
1138bf80f4bSopenharmony_ci{
1148bf80f4bSopenharmony_ci    // Synchronously destroy the lume object in engine thread.. (only for strong refs.)
1158bf80f4bSopenharmony_ci    if (obj_) {
1168bf80f4bSopenharmony_ci        ExecSyncTask([obj = BASE_NS::move(obj_)]() { return META_NS::IAny::Ptr {}; });
1178bf80f4bSopenharmony_ci    }
1188bf80f4bSopenharmony_ci    // and reset the weak ref too. (which may be null anyway)
1198bf80f4bSopenharmony_ci    objW_.reset();
1208bf80f4bSopenharmony_ci}
1218bf80f4bSopenharmony_ciNapiApi::Function GetJSConstructor(napi_env env, const BASE_NS::string_view jsName)
1228bf80f4bSopenharmony_ci{
1238bf80f4bSopenharmony_ci    NapiApi::MyInstanceState* mis;
1248bf80f4bSopenharmony_ci    napi_get_instance_data(env, (void**)&mis);
1258bf80f4bSopenharmony_ci    return NapiApi::Function(env, mis->FetchCtor(jsName));
1268bf80f4bSopenharmony_ci}
1278bf80f4bSopenharmony_ciNapiApi::Object CreateJsObj(napi_env env, const BASE_NS::string_view jsName, META_NS::IObject::Ptr real, bool strong,
1288bf80f4bSopenharmony_ci    uint32_t argc, napi_value* argv)
1298bf80f4bSopenharmony_ci{
1308bf80f4bSopenharmony_ci    NapiApi::Object obj(GetJSConstructor(env, jsName), argc, argv);
1318bf80f4bSopenharmony_ci    if (!obj) {
1328bf80f4bSopenharmony_ci        return {};
1338bf80f4bSopenharmony_ci    }
1348bf80f4bSopenharmony_ci    auto oo = GetRootObject(env, obj);
1358bf80f4bSopenharmony_ci    oo->SetNativeObject(real, strong);
1368bf80f4bSopenharmony_ci    return obj;
1378bf80f4bSopenharmony_ci}
1388bf80f4bSopenharmony_cibool IsInstanceOf(const NapiApi::Object& obj, const BASE_NS::string_view jsName)
1398bf80f4bSopenharmony_ci{
1408bf80f4bSopenharmony_ci    auto env = obj.GetEnv();
1418bf80f4bSopenharmony_ci    auto cl = GetJSConstructor(env, jsName);
1428bf80f4bSopenharmony_ci    bool result = false;
1438bf80f4bSopenharmony_ci    auto status = napi_instanceof(env, obj, cl, &result);
1448bf80f4bSopenharmony_ci    return result;
1458bf80f4bSopenharmony_ci}
1468bf80f4bSopenharmony_ci
1478bf80f4bSopenharmony_ciNapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj)
1488bf80f4bSopenharmony_ci{
1498bf80f4bSopenharmony_ci    using namespace META_NS;
1508bf80f4bSopenharmony_ci
1518bf80f4bSopenharmony_ci    // access hidden property.
1528bf80f4bSopenharmony_ci    if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
1538bf80f4bSopenharmony_ci        if (auto wrapper = AppMeta->GetPropertyByName<SharedPtrIInterface>("_JSW")) {
1548bf80f4bSopenharmony_ci            // The native object already contains a JS object.
1558bf80f4bSopenharmony_ci            return interface_cast<JSWrapperState>(wrapper->GetValue())->GetObject();
1568bf80f4bSopenharmony_ci        }
1578bf80f4bSopenharmony_ci    }
1588bf80f4bSopenharmony_ci    return nullptr;
1598bf80f4bSopenharmony_ci}
1608bf80f4bSopenharmony_ciNapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, NapiApi::Object jsobj)
1618bf80f4bSopenharmony_ci{
1628bf80f4bSopenharmony_ci    using namespace META_NS;
1638bf80f4bSopenharmony_ci    if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
1648bf80f4bSopenharmony_ci        // Add a reference to the JS object to the native object.
1658bf80f4bSopenharmony_ci        auto res = BASE_NS::shared_ptr<JSWrapperState>(new JSWrapperState(jsobj, obj->GetClassName()));
1668bf80f4bSopenharmony_ci        auto& obr = GetObjectRegistry();
1678bf80f4bSopenharmony_ci        auto wrapper = AppMeta->GetPropertyByName<SharedPtrIInterface>("_JSW");
1688bf80f4bSopenharmony_ci        // check if the property exists.
1698bf80f4bSopenharmony_ci        if (wrapper) {
1708bf80f4bSopenharmony_ci            // .. does the wrapper exist? (ie. reference from native to js)
1718bf80f4bSopenharmony_ci            if (auto val = interface_cast<JSWrapperState>(wrapper->GetValue())) {
1728bf80f4bSopenharmony_ci                // validate it
1738bf80f4bSopenharmony_ci                auto ref = val->GetObject();
1748bf80f4bSopenharmony_ci                bool equals = false;
1758bf80f4bSopenharmony_ci                if (ref) {
1768bf80f4bSopenharmony_ci                    // we have ref.. so make the compare here
1778bf80f4bSopenharmony_ci                    napi_strict_equals(jsobj.GetEnv(), jsobj, ref, &equals);
1788bf80f4bSopenharmony_ci                    if (!equals) {
1798bf80f4bSopenharmony_ci                        // this may be a problem
1808bf80f4bSopenharmony_ci                        // (there should only be a 1-1 mapping between js objects and native objects)
1818bf80f4bSopenharmony_ci                        CORE_LOG_F("_JSW exists and points to different object!");
1828bf80f4bSopenharmony_ci                    } else {
1838bf80f4bSopenharmony_ci                        // objects already linked
1848bf80f4bSopenharmony_ci                        return ref;
1858bf80f4bSopenharmony_ci                    }
1868bf80f4bSopenharmony_ci                } else {
1878bf80f4bSopenharmony_ci                    // creating a new jsobject for existing native object that was bound before (but js object was GC:d)
1888bf80f4bSopenharmony_ci                    // this is fine.
1898bf80f4bSopenharmony_ci                    CORE_LOG_V(
1908bf80f4bSopenharmony_ci                        "Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)");
1918bf80f4bSopenharmony_ci                }
1928bf80f4bSopenharmony_ci            }
1938bf80f4bSopenharmony_ci        } else {
1948bf80f4bSopenharmony_ci            // create the wrapper property
1958bf80f4bSopenharmony_ci            wrapper = ConstructProperty<SharedPtrIInterface>(
1968bf80f4bSopenharmony_ci                "_JSW", nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE);
1978bf80f4bSopenharmony_ci            AppMeta->AddProperty(wrapper);
1988bf80f4bSopenharmony_ci        }
1998bf80f4bSopenharmony_ci        // link native to js.
2008bf80f4bSopenharmony_ci        wrapper->SetValue(interface_pointer_cast<CORE_NS::IInterface>(res));
2018bf80f4bSopenharmony_ci        return res->GetObject();
2028bf80f4bSopenharmony_ci    }
2038bf80f4bSopenharmony_ci    napi_value val;
2048bf80f4bSopenharmony_ci    napi_get_null(jsobj.GetEnv(), &val);
2058bf80f4bSopenharmony_ci    return { jsobj.GetEnv(), val };
2068bf80f4bSopenharmony_ci}
2078bf80f4bSopenharmony_ciNapiApi::Object CreateFromNativeInstance(
2088bf80f4bSopenharmony_ci    napi_env env, const META_NS::IObject::Ptr& obj, bool strong, uint32_t argc, napi_value* argv)
2098bf80f4bSopenharmony_ci{
2108bf80f4bSopenharmony_ci    napi_value null;
2118bf80f4bSopenharmony_ci    napi_get_null(env, &null);
2128bf80f4bSopenharmony_ci    if (obj == nullptr) {
2138bf80f4bSopenharmony_ci        return {};
2148bf80f4bSopenharmony_ci    }
2158bf80f4bSopenharmony_ci    using namespace META_NS;
2168bf80f4bSopenharmony_ci    NapiApi::Object nodeJS = FetchJsObj(obj);
2178bf80f4bSopenharmony_ci    if (nodeJS) {
2188bf80f4bSopenharmony_ci        // we have a cached js object for this native object
2198bf80f4bSopenharmony_ci        return nodeJS;
2208bf80f4bSopenharmony_ci    }
2218bf80f4bSopenharmony_ci    // no js object. create it.
2228bf80f4bSopenharmony_ci    BASE_NS::string name { obj->GetClassName() };
2238bf80f4bSopenharmony_ci    // specialize/remap class names & interfaces.
2248bf80f4bSopenharmony_ci    if (name == "Bitmap") {
2258bf80f4bSopenharmony_ci        name = "Image";
2268bf80f4bSopenharmony_ci    } else if (name == "Tonemap") {
2278bf80f4bSopenharmony_ci        name = "ToneMappingSettings";
2288bf80f4bSopenharmony_ci    } else if (name == "PostProcess") {
2298bf80f4bSopenharmony_ci        name = "PostProcessSettings";
2308bf80f4bSopenharmony_ci    } else if (name == "Material") {
2318bf80f4bSopenharmony_ci        // okay. specialize then...
2328bf80f4bSopenharmony_ci        SCENE_NS::IMaterial* mat = interface_cast<SCENE_NS::IMaterial>(obj);
2338bf80f4bSopenharmony_ci        auto shdr = mat->MaterialShader()->GetValue();
2348bf80f4bSopenharmony_ci        auto shdruri = shdr->Uri()->GetValue();
2358bf80f4bSopenharmony_ci        if (!shdruri.empty()) {
2368bf80f4bSopenharmony_ci            name = "ShaderMaterial";
2378bf80f4bSopenharmony_ci        } else {
2388bf80f4bSopenharmony_ci            // hide other material types..
2398bf80f4bSopenharmony_ci            return {};
2408bf80f4bSopenharmony_ci        }
2418bf80f4bSopenharmony_ci    } else if (name == "Shader") {
2428bf80f4bSopenharmony_ci        // possible specialization?
2438bf80f4bSopenharmony_ci        name = "Shader";
2448bf80f4bSopenharmony_ci    } else if (name == "Node") {
2458bf80f4bSopenharmony_ci        if (interface_cast<SCENE_NS::INode>(obj)->GetMesh()) {
2468bf80f4bSopenharmony_ci            name = "Geometry";
2478bf80f4bSopenharmony_ci        } else {
2488bf80f4bSopenharmony_ci            name = "Node";
2498bf80f4bSopenharmony_ci        }
2508bf80f4bSopenharmony_ci    }
2518bf80f4bSopenharmony_ci
2528bf80f4bSopenharmony_ci    MakeNativeObjectParam(env, obj, argc, argv);
2538bf80f4bSopenharmony_ci
2548bf80f4bSopenharmony_ci    nodeJS = CreateJsObj(env, name, obj, strong, argc, argv);
2558bf80f4bSopenharmony_ci    if (!nodeJS) {
2568bf80f4bSopenharmony_ci        // EEK. could not create the object.
2578bf80f4bSopenharmony_ci        CORE_LOG_E("Could not create JSObject for Class %s", name.c_str());
2588bf80f4bSopenharmony_ci        return {};
2598bf80f4bSopenharmony_ci    }
2608bf80f4bSopenharmony_ci    return StoreJsObj(obj, nodeJS);
2618bf80f4bSopenharmony_ci}