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