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 }