1 /*
2  * Copyright (C) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include "BaseObjectJS.h"
16 
17 #include <meta/interface/intf_metadata.h>
18 #include <meta/interface/property/construct_property.h>
19 #include <scene_plugin/interface/intf_mesh.h>
20 #include <scene_plugin/interface/intf_nodes.h>
21 
22 // this class is used to store a reference to a JS object in the metaobject.
23 class JSWrapperState : public CORE_NS::IInterface {
24 public:
25     static constexpr BASE_NS::Uid UID { "2ef39765-91f2-46c4-b85f-7cad40dd3bcd" };
26     const IInterface* GetInterface(const BASE_NS::Uid& uid) const override;
27     IInterface* GetInterface(const BASE_NS::Uid& uid) override;
28     void Ref() override;
29     void Unref() override;
30     JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name);
31     ~JSWrapperState();
32     NapiApi::Object GetObject();
33 
34 private:
35     BASE_NS::string name_;
36     volatile int32_t count_ { 0 };
37     napi_env env_;
38     napi_ref ref_;
39 };
40 
GetInterface(const BASE_NS::Uid & uid) const41 const CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const
42 {
43     if (uid == CORE_NS::IInterface::UID) {
44         return this;
45     }
46     if (uid == JSWrapperState::UID) {
47         return this;
48     }
49     return nullptr;
50 }
GetInterface(const BASE_NS::Uid & uid)51 CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid)
52 {
53     if (uid == CORE_NS::IInterface::UID) {
54         return this;
55     }
56     if (uid == JSWrapperState::UID) {
57         return this;
58     }
59     return nullptr;
60 }
Ref()61 void JSWrapperState::Ref()
62 {
63     CORE_NS::AtomicIncrement(&count_);
64 }
Unref()65 void JSWrapperState::Unref()
66 {
67     if (CORE_NS::AtomicDecrement(&count_) == 0) {
68         delete this;
69     }
70 }
JSWrapperState(NapiApi::Object obj,BASE_NS::string_view name)71 JSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name)
72 {
73     name_ = name;
74     LOG_F("JSWrapperState ++ %s", name_.c_str());
75     env_ = obj.GetEnv();
76     // Create a WEAK reference to the object
77     napi_create_reference(env_, obj, 0, &ref_);
78 }
~JSWrapperState()79 JSWrapperState::~JSWrapperState()
80 {
81     // release the reference.
82 
83     napi_delete_reference(env_, ref_);
84     LOG_F("JSWrapperState -- %s", name_.c_str());
85 }
GetObject()86 NapiApi::Object JSWrapperState::GetObject()
87 {
88     napi_value value;
89     napi_get_reference_value(env_, ref_, &value);
90     return { env_, value };
91 }
92 
TrueRootObject()93 TrueRootObject::TrueRootObject() {}
SetNativeObject(META_NS::IObject::Ptr real,bool strong)94 void TrueRootObject::SetNativeObject(META_NS::IObject::Ptr real, bool strong)
95 {
96     if (strong) {
97         obj_ = real;
98     } else {
99         objW_ = real;
100     }
101 }
GetNativeObject()102 META_NS::IObject::Ptr TrueRootObject::GetNativeObject()
103 {
104     // if we have a strong ref...
105     if (obj_) {
106         // return that directly.
107         return obj_;
108     }
109     // otherwise try to lock the weak reference.
110     return objW_.lock();
111 }
Finalize(napi_env env)112 void TrueRootObject::Finalize(napi_env env)
113 {
114     // Synchronously destroy the lume object in engine thread.. (only for strong refs.)
115     if (obj_) {
116         ExecSyncTask([obj = BASE_NS::move(obj_)]() { return META_NS::IAny::Ptr {}; });
117     }
118     // and reset the weak ref too. (which may be null anyway)
119     objW_.reset();
120 }
GetJSConstructor(napi_env env,const BASE_NS::string_view jsName)121 NapiApi::Function GetJSConstructor(napi_env env, const BASE_NS::string_view jsName)
122 {
123     NapiApi::MyInstanceState* mis;
124     napi_get_instance_data(env, (void**)&mis);
125     return NapiApi::Function(env, mis->FetchCtor(jsName));
126 }
CreateJsObj(napi_env env,const BASE_NS::string_view jsName,META_NS::IObject::Ptr real,bool strong,uint32_t argc,napi_value * argv)127 NapiApi::Object CreateJsObj(napi_env env, const BASE_NS::string_view jsName, META_NS::IObject::Ptr real, bool strong,
128     uint32_t argc, napi_value* argv)
129 {
130     NapiApi::Object obj(GetJSConstructor(env, jsName), argc, argv);
131     if (!obj) {
132         return {};
133     }
134     auto oo = GetRootObject(env, obj);
135     oo->SetNativeObject(real, strong);
136     return obj;
137 }
IsInstanceOf(const NapiApi::Object & obj,const BASE_NS::string_view jsName)138 bool IsInstanceOf(const NapiApi::Object& obj, const BASE_NS::string_view jsName)
139 {
140     auto env = obj.GetEnv();
141     auto cl = GetJSConstructor(env, jsName);
142     bool result = false;
143     auto status = napi_instanceof(env, obj, cl, &result);
144     return result;
145 }
146 
FetchJsObj(const META_NS::IObject::Ptr & obj)147 NapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj)
148 {
149     using namespace META_NS;
150 
151     // access hidden property.
152     if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
153         if (auto wrapper = AppMeta->GetPropertyByName<SharedPtrIInterface>("_JSW")) {
154             // The native object already contains a JS object.
155             return interface_cast<JSWrapperState>(wrapper->GetValue())->GetObject();
156         }
157     }
158     return nullptr;
159 }
StoreJsObj(const META_NS::IObject::Ptr & obj,NapiApi::Object jsobj)160 NapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, NapiApi::Object jsobj)
161 {
162     using namespace META_NS;
163     if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
164         // Add a reference to the JS object to the native object.
165         auto res = BASE_NS::shared_ptr<JSWrapperState>(new JSWrapperState(jsobj, obj->GetClassName()));
166         auto& obr = GetObjectRegistry();
167         auto wrapper = AppMeta->GetPropertyByName<SharedPtrIInterface>("_JSW");
168         // check if the property exists.
169         if (wrapper) {
170             // .. does the wrapper exist? (ie. reference from native to js)
171             if (auto val = interface_cast<JSWrapperState>(wrapper->GetValue())) {
172                 // validate it
173                 auto ref = val->GetObject();
174                 bool equals = false;
175                 if (ref) {
176                     // we have ref.. so make the compare here
177                     napi_strict_equals(jsobj.GetEnv(), jsobj, ref, &equals);
178                     if (!equals) {
179                         // this may be a problem
180                         // (there should only be a 1-1 mapping between js objects and native objects)
181                         CORE_LOG_F("_JSW exists and points to different object!");
182                     } else {
183                         // objects already linked
184                         return ref;
185                     }
186                 } else {
187                     // creating a new jsobject for existing native object that was bound before (but js object was GC:d)
188                     // this is fine.
189                     CORE_LOG_V(
190                         "Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)");
191                 }
192             }
193         } else {
194             // create the wrapper property
195             wrapper = ConstructProperty<SharedPtrIInterface>(
196                 "_JSW", nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE);
197             AppMeta->AddProperty(wrapper);
198         }
199         // link native to js.
200         wrapper->SetValue(interface_pointer_cast<CORE_NS::IInterface>(res));
201         return res->GetObject();
202     }
203     napi_value val;
204     napi_get_null(jsobj.GetEnv(), &val);
205     return { jsobj.GetEnv(), val };
206 }
CreateFromNativeInstance(napi_env env,const META_NS::IObject::Ptr & obj,bool strong,uint32_t argc,napi_value * argv)207 NapiApi::Object CreateFromNativeInstance(
208     napi_env env, const META_NS::IObject::Ptr& obj, bool strong, uint32_t argc, napi_value* argv)
209 {
210     napi_value null;
211     napi_get_null(env, &null);
212     if (obj == nullptr) {
213         return {};
214     }
215     using namespace META_NS;
216     NapiApi::Object nodeJS = FetchJsObj(obj);
217     if (nodeJS) {
218         // we have a cached js object for this native object
219         return nodeJS;
220     }
221     // no js object. create it.
222     BASE_NS::string name { obj->GetClassName() };
223     // specialize/remap class names & interfaces.
224     if (name == "Bitmap") {
225         name = "Image";
226     } else if (name == "Tonemap") {
227         name = "ToneMappingSettings";
228     } else if (name == "PostProcess") {
229         name = "PostProcessSettings";
230     } else if (name == "Material") {
231         // okay. specialize then...
232         SCENE_NS::IMaterial* mat = interface_cast<SCENE_NS::IMaterial>(obj);
233         auto shdr = mat->MaterialShader()->GetValue();
234         auto shdruri = shdr->Uri()->GetValue();
235         if (!shdruri.empty()) {
236             name = "ShaderMaterial";
237         } else {
238             // hide other material types..
239             return {};
240         }
241     } else if (name == "Shader") {
242         // possible specialization?
243         name = "Shader";
244     } else if (name == "Node") {
245         if (interface_cast<SCENE_NS::INode>(obj)->GetMesh()) {
246             name = "Geometry";
247         } else {
248             name = "Node";
249         }
250     }
251 
252     MakeNativeObjectParam(env, obj, argc, argv);
253 
254     nodeJS = CreateJsObj(env, name, obj, strong, argc, argv);
255     if (!nodeJS) {
256         // EEK. could not create the object.
257         CORE_LOG_E("Could not create JSObject for Class %s", name.c_str());
258         return {};
259     }
260     return StoreJsObj(obj, nodeJS);
261 }