• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/interface/intf_mesh.h>
20 #include <scene/interface/intf_light.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     static void Call(napi_env env, napi_value js_callback, void* context, void* inData);
36     static void Final(napi_env env, void* finalize_data, void* finalize_hint);
37     BASE_NS::string name_;
38     volatile int32_t count_ { 0 };
39     napi_env env_ { nullptr };
40     napi_ref ref_ { nullptr };
41     napi_threadsafe_function termfun { nullptr };
42     struct fun_parm {
43         napi_ref ref { nullptr };
44         napi_threadsafe_function termfun { nullptr };
45     };
46 };
47 
GetInterface(const BASE_NS::Uid & uid) const48 const CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const
49 {
50     if (uid == CORE_NS::IInterface::UID) {
51         return this;
52     }
53     if (uid == JSWrapperState::UID) {
54         return this;
55     }
56     return nullptr;
57 }
GetInterface(const BASE_NS::Uid & uid)58 CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid)
59 {
60     if (uid == CORE_NS::IInterface::UID) {
61         return this;
62     }
63     if (uid == JSWrapperState::UID) {
64         return this;
65     }
66     return nullptr;
67 }
Ref()68 void JSWrapperState::Ref()
69 {
70     CORE_NS::AtomicIncrement(&count_);
71 }
Unref()72 void JSWrapperState::Unref()
73 {
74     if (CORE_NS::AtomicDecrement(&count_) == 0) {
75         delete this;
76     }
77 }
78 
JSWrapperState(NapiApi::Object obj,BASE_NS::string_view name)79 JSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name) : name_(name), env_(obj.GetEnv())
80 {
81     napi_status status;
82     // Create a WEAK reference to the object
83     status = napi_create_reference(env_, obj.ToNapiValue(), 0, &ref_);
84 
85     // Create a thread safe function to release the js reference, as the destructor MAY be called from engine thread.
86     napi_value jname = nullptr;
87     status = napi_create_string_latin1(env_, "JSW", NAPI_AUTO_LENGTH, &jname);
88     status = napi_create_threadsafe_function(
89         env_, nullptr, nullptr, jname, 0, 1, nullptr, &JSWrapperState::Final, nullptr, &JSWrapperState::Call, &termfun);
90 }
Call(napi_env env,napi_value js_callback,void * context,void * inData)91 void JSWrapperState::Call(napi_env env, napi_value js_callback, void* context, void* inData)
92 {
93     napi_status status;
94     fun_parm* ref = (fun_parm*)inData;
95     status = napi_delete_reference(env, ref->ref);
96     status = napi_release_threadsafe_function(ref->termfun, napi_threadsafe_function_release_mode::napi_tsfn_release);
97     delete ref;
98 }
Final(napi_env env,void * finalize_data,void * context)99 void JSWrapperState::Final(napi_env env, void* finalize_data, void* context) {};
100 
~JSWrapperState()101 JSWrapperState::~JSWrapperState()
102 {
103     napi_status status;
104     // trigger the threadsafe function, so that the javascript "weak reference" will be destroyed.
105     status = napi_call_threadsafe_function(
106         termfun, new fun_parm { ref_, termfun }, napi_threadsafe_function_call_mode::napi_tsfn_blocking);
107 }
GetObject()108 NapiApi::Object JSWrapperState::GetObject()
109 {
110     napi_status status;
111     napi_value value;
112     status = napi_get_reference_value(env_, ref_, &value);
113     return { env_, value };
114 }
115 
TrueRootObject()116 TrueRootObject::TrueRootObject() {}
destroy(TrueRootObject * object)117 void TrueRootObject::destroy(TrueRootObject* object)
118 {
119     delete object;
120 }
121 
IsStrong() const122 bool TrueRootObject::IsStrong() const
123 {
124     return obj_ != nullptr;
125 }
SetNativeObject(META_NS::IObject::Ptr real,bool strong)126 void TrueRootObject::SetNativeObject(META_NS::IObject::Ptr real, bool strong)
127 {
128     if (strong) {
129         obj_ = real;
130     } else {
131         objW_ = real;
132     }
133 }
GetNativeObject()134 META_NS::IObject::Ptr TrueRootObject::GetNativeObject()
135 {
136     // if we have a strong ref...
137     if (obj_) {
138         // return that directly.
139         return obj_;
140     }
141     // otherwise try to lock the weak reference.
142     return objW_.lock();
143 }
Finalize(napi_env env)144 void TrueRootObject::Finalize(napi_env env)
145 {
146     // Synchronously destroy the lume object in engine thread.. (only for strong refs.)
147     if (obj_) {
148         ExecSyncTask([obj = BASE_NS::move(obj_)]() { return META_NS::IAny::Ptr {}; });
149     }
150     // and reset the weak ref too. (which may be null anyway)
151     objW_.reset();
152 }
GetJSConstructor(napi_env env,const BASE_NS::string_view jsName)153 NapiApi::Function GetJSConstructor(napi_env env, const BASE_NS::string_view jsName)
154 {
155     NapiApi::MyInstanceState* mis;
156     GetInstanceData(env, (void**)&mis);
157     return NapiApi::Function(env, mis->FetchCtor(jsName));
158 }
CreateJsObj(napi_env env,const BASE_NS::string_view jsName,META_NS::IObject::Ptr real,bool strong,uint32_t argc,napi_value * argv)159 NapiApi::Object CreateJsObj(napi_env env, const BASE_NS::string_view jsName, META_NS::IObject::Ptr real, bool strong,
160     uint32_t argc, napi_value* argv)
161 {
162     NapiApi::Object obj(GetJSConstructor(env, jsName), argc, argv);
163     if (!obj) {
164         return {};
165     }
166     auto oo = GetRootObject(obj);
167     oo->SetNativeObject(real, strong);
168     return obj;
169 }
IsInstanceOf(const NapiApi::Object & obj,const BASE_NS::string_view jsName)170 bool IsInstanceOf(const NapiApi::Object& obj, const BASE_NS::string_view jsName)
171 {
172     auto env = obj.GetEnv();
173     auto cl = GetJSConstructor(env, jsName);
174     bool result = false;
175     auto status = napi_instanceof(env, obj.ToNapiValue(), cl, &result);
176     return result;
177 }
178 
FetchJsObj(const META_NS::IObject::Ptr & obj,BASE_NS::string_view name)179 NapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj, BASE_NS::string_view name)
180 {
181     using namespace META_NS;
182 
183     // access hidden property.
184     if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
185         if (auto wrapper = AppMeta->GetProperty<SharedPtrIInterface>(name, MetadataQuery::EXISTING)) {
186             // The native object already contains a JS object.
187             return interface_cast<JSWrapperState>(wrapper->GetValue())->GetObject();
188         }
189     }
190     return {};
191 }
StoreJsObj(const META_NS::IObject::Ptr & obj,NapiApi::Object jsobj,BASE_NS::string_view name)192 NapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, NapiApi::Object jsobj, BASE_NS::string_view name)
193 {
194     using namespace META_NS;
195     if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
196         // Add a reference to the JS object to the native object.
197         const auto& obr = GetObjectRegistry();
198         auto wrapper = AppMeta->GetProperty<SharedPtrIInterface>(name, META_NS::MetadataQuery::EXISTING);
199         // check if the property exists.
200         if (wrapper) {
201             // .. does the wrapper exist? (ie. reference from native to js)
202             if (auto val = interface_cast<JSWrapperState>(wrapper->GetValue())) {
203                 // validate it
204                 auto ref = val->GetObject();
205                 bool equals = false;
206                 if (ref) {
207                     // we have ref.. so make the compare here
208                     napi_strict_equals(jsobj.GetEnv(), jsobj.ToNapiValue(), ref.ToNapiValue(), &equals);
209                     if (!equals) {
210                         // this may be a problem
211                         // (there should only be a 1-1 mapping between js objects and native objects)
212                         LOG_F("_JSW exists and points to different object!");
213                     } else {
214                         // objects already linked
215                         return ref;
216                     }
217                 } else {
218                     // creating a new jsobject for existing native object that was bound before (but js object was GC:d)
219                     // this is fine.
220                     LOG_V(
221                         "Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)");
222                 }
223             }
224         } else {
225             // create the wrapper property
226             wrapper = ConstructProperty<SharedPtrIInterface>(
227                 name, nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE);
228             AppMeta->AddProperty(wrapper);
229         }
230         // link native to js.
231         auto res = BASE_NS::shared_ptr<CORE_NS::IInterface>(new JSWrapperState(jsobj, obj->GetClassName()));
232         wrapper->SetValue(res);
233         return interface_cast<JSWrapperState>(res)->GetObject();
234     }
235     napi_value val;
236     napi_get_null(jsobj.GetEnv(), &val);
237     return { jsobj.GetEnv(), val };
238 }
239 
GetConstructorName(const META_NS::IObject::Ptr & obj)240 BASE_NS::string GetConstructorName(const META_NS::IObject::Ptr& obj)
241 {
242     BASE_NS::string name { obj->GetClassName() };
243     // specialize/remap class names & interfaces.
244     if (name == "Bitmap") {
245         name = "Image";
246     } else if (name == "Tonemap") {
247         name = "ToneMappingSettings";
248     } else if (name == "PostProcess") {
249         name = "PostProcessSettings";
250     } else if (name == "Material") {
251         // ok
252     } else if (name == "Shader") {
253         // possible specialization?
254     } else if (name == "EcsAnimation") {
255         name = "Animation";
256     } else if (name == "MeshNode") {
257         name = "Geometry";
258     } else if (name == "CameraNode") {
259         name = "Camera";
260     } else if (name == "LightNode") {
261         SCENE_NS::ILight* lgt = interface_cast<SCENE_NS::ILight>(obj);
262         if (lgt == nullptr) {
263             LOG_E("lgt is null");
264             return name;
265         }
266         auto type = lgt->Type()->GetValue();
267         if (type == Scene::LightType::DIRECTIONAL) {
268             name = "DirectionalLight";
269         } else if (type == Scene::LightType::POINT) {
270             name = "PointLight";
271         } else if (type == Scene::LightType::SPOT) {
272             name = "SpotLight";
273         } else {
274             name = "Node";
275         }
276     } else if (name.ends_with("Node")) {
277         name = "Node";
278     }
279     return name;
280 }
281 
CreateFromNativeInstance(napi_env env,const META_NS::IObject::Ptr & obj,bool strong,uint32_t argc,napi_value * argv,BASE_NS::string_view pname)282 NapiApi::Object CreateFromNativeInstance(napi_env env, const META_NS::IObject::Ptr& obj,
283     bool strong, uint32_t argc, napi_value* argv, BASE_NS::string_view pname)
284 {
285     napi_value null;
286     napi_get_null(env, &null);
287     if (obj == nullptr) {
288         return {};
289     }
290     using namespace META_NS;
291     NapiApi::Object nodeJS = FetchJsObj(obj, pname);
292     if (nodeJS) {
293         // we have a cached js object for this native object
294         return nodeJS;
295     }
296     // no js object. create it.
297     auto name = GetConstructorName(obj);
298     MakeNativeObjectParam(env, obj, argc, argv);
299 
300     nodeJS = CreateJsObj(env, name, obj, strong, argc, argv);
301     if (!nodeJS) {
302         // EEK. could not create the object.
303         LOG_E("Could not create JSObject for Class %s", name.c_str());
304         return {};
305     }
306     return StoreJsObj(obj, nodeJS, pname);
307 }
DebugNativesHavingJS(void)308 void DebugNativesHavingJS(void)
309 {
310     for (auto&& v : META_NS::GetObjectRegistry().GetAllObjectInstances()) {
311         if (auto i = interface_cast<META_NS::IMetadata>(v)) {
312             auto p = i->GetProperty<META_NS::SharedPtrIInterface>("_JSW", META_NS::MetadataQuery::EXISTING);
313             if (!p) {
314                 p = i->GetProperty<META_NS::SharedPtrIInterface>("_JSWMesh", META_NS::MetadataQuery::EXISTING);
315             }
316             if (p) {
317                 LOG_W("obj: %s (%p, strong count %u)", v->GetName().c_str(), v.get(), v.use_count());
318                 if (auto val = interface_cast<JSWrapperState>(p->GetValue())) {
319                     if (auto ref = val->GetObject()) {
320                         auto* tro = ref.Native<TrueRootObject>();
321                         if (tro) {
322                             if (auto m = interface_pointer_cast<META_NS::IObject>(tro->GetNativeObject())) {
323                                 LOG_W("still has valid object: %s (%p, strong count %u) strong: %d (tro: %p)",
324                                     m->GetName().c_str(), m.get(), m.use_count(), tro->IsStrong(), tro);
325                             }
326                         }
327                     }
328                 }
329             }
330         }
331     }
332 }
333