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
16 #include "PropertyProxy.h"
17
18 #include <meta/api/make_callback.h>
19 #include <meta/api/util.h>
20 #include <meta/interface/intf_metadata.h>
21 #include <meta/interface/intf_task_queue_registry.h>
22 #include <napi_api.h>
23 #include <scene/ext/intf_internal_scene.h>
24
25 #include "BaseObjectJS.h"
26 #include "Vec2Proxy.h"
27 #include "Vec3Proxy.h"
28 #include "Vec4Proxy.h"
29
~PropertyProxy()30 PropertyProxy::~PropertyProxy()
31 {
32 // should be a no-op.
33 RemoveHandlers();
34 }
35
UpdateLocal()36 void PropertyProxy::UpdateLocal()
37 {
38 // should execute in engine thread.
39 Lock();
40 if (prop_) {
41 UpdateLocalValues();
42 }
43 Unlock();
44 }
UpdateRemote()45 int32_t PropertyProxy::UpdateRemote()
46 {
47 // executed in engine thread (ie. happens between frames)
48 Lock();
49 if (prop_) {
50 // make sure the handler is not called..
51 auto token = changeToken_;
52 if (token) {
53 prop_->OnChanged()->RemoveHandler(token);
54 }
55
56 UpdateRemoteValues();
57
58 if (auto ext = interface_pointer_cast<SCENE_NS::IInternalScene>(GetExtra())) {
59 ext->SyncProperty(prop_, META_NS::EngineSyncDirection::AUTO);
60 }
61
62 // add it back.
63 if (token) {
64 changeToken_ = prop_->OnChanged()->AddHandler(changeHandler_);
65 }
66 }
67 updateToken_ = nullptr;
68 Unlock();
69 return 0;
70 }
71
ScheduleUpdate()72 void PropertyProxy::ScheduleUpdate()
73 {
74 // create a task to engine queue to sync the property.
75 if (updateToken_ == nullptr) {
76 // sync not queued, so queue sync.
77 updateToken_ = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->AddTask(updateTask_);
78 }
79 }
80
PropertyProxy(META_NS::IProperty::Ptr prop)81 PropertyProxy::PropertyProxy(META_NS::IProperty::Ptr prop) : prop_(prop)
82 {
83 assert(prop_);
84 changeHandler_ = META_NS::MakeCallback<META_NS::IOnChanged>(this, &PropertyProxy::UpdateLocal);
85 updateTask_ = META_NS::MakeCallback<META_NS::ITaskQueueTask>(this, &PropertyProxy::UpdateRemote);
86 changeToken_ = prop_->OnChanged()->AddHandler(changeHandler_);
87 }
88
SetExtra(const BASE_NS::shared_ptr<CORE_NS::IInterface> extra)89 void PropertyProxy::SetExtra(const BASE_NS::shared_ptr<CORE_NS::IInterface> extra)
90 {
91 extra_ = extra;
92 }
93
GetExtra() const94 const BASE_NS::shared_ptr<CORE_NS::IInterface> PropertyProxy::GetExtra() const
95 {
96 return extra_.lock();
97 }
98
RemoveHandlers()99 void PropertyProxy::RemoveHandlers()
100 {
101 if (!changeHandler_) {
102 return;
103 }
104 ExecSyncTask([this]() {
105 if (updateToken_) {
106 META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->CancelTask(updateToken_);
107 updateToken_ = nullptr;
108 }
109 if (prop_ && changeToken_) {
110 prop_->OnChanged()->RemoveHandler(changeToken_);
111 }
112 changeToken_ = {};
113 prop_.reset();
114 changeHandler_.reset();
115 updateTask_.reset();
116 return META_NS::IAny::Ptr {};
117 });
118 }
SyncGet()119 void PropertyProxy::SyncGet()
120 {
121 // initialize current values.
122 ExecSyncTask([this]() {
123 // executed in engine thread
124 UpdateLocal();
125 return META_NS::IAny::Ptr {};
126 });
127 }
Lock()128 void PropertyProxy::Lock()
129 {
130 lock_.Lock();
131 }
Unlock()132 void PropertyProxy::Unlock()
133 {
134 lock_.Unlock();
135 }
136
MemberProxy(ObjectPropertyProxy * p,BASE_NS::string m)137 ObjectPropertyProxy::MemberProxy::MemberProxy(ObjectPropertyProxy* p, BASE_NS::string m) : proxy_(p), memb_(m) {}
~MemberProxy()138 ObjectPropertyProxy::MemberProxy::~MemberProxy() {}
Name() const139 const BASE_NS::string_view ObjectPropertyProxy::MemberProxy::Name() const
140 {
141 return memb_;
142 }
143
Getter(napi_env e,napi_callback_info i)144 napi_value ObjectPropertyProxy::MemberProxy::Getter(napi_env e, napi_callback_info i)
145 {
146 NapiApi::FunctionContext<> info(e, i);
147 auto pc = static_cast<ObjectPropertyProxy::MemberProxy*>(info.GetData());
148 if ((pc) && (pc->proxy_)) {
149 return pc->proxy_->GetMemberValue(info.Env(), pc->memb_);
150 }
151 return info.GetUndefined();
152 }
Setter(napi_env e,napi_callback_info i)153 napi_value ObjectPropertyProxy::MemberProxy::Setter(napi_env e, napi_callback_info i)
154 {
155 NapiApi::FunctionContext<> info(e, i);
156 auto pc = static_cast<ObjectPropertyProxy::MemberProxy*>(info.GetData());
157 if ((pc) && (pc->proxy_)) {
158 pc->proxy_->SetMemberValue(info, pc->memb_);
159 }
160 return info.GetUndefined();
161 }
162
Create(napi_env env,const BASE_NS::string jsName)163 void ObjectPropertyProxy::Create(napi_env env, const BASE_NS::string jsName)
164 {
165 if (jsName.empty()) {
166 obj_ = NapiApi::StrongRef { NapiApi::Object(env) };
167 } else {
168 NapiApi::MyInstanceState* mis;
169 GetInstanceData(env, reinterpret_cast<void**>(&mis));
170 auto ref = NapiApi::Object(env, mis->getRef());
171 auto cl = ref.Get(jsName.c_str());
172 if (cl) {
173 napi_value value;
174 napi_new_instance(env, cl, 0, nullptr, &value);
175 obj_ = NapiApi::StrongRef { NapiApi::Object(env, value) };
176 }
177 }
178 if (obj_.IsEmpty()) {
179 CORE_LOG_F("Could not create property object for %s", jsName.c_str());
180 }
181 }
182
Hook(const BASE_NS::string member)183 void ObjectPropertyProxy::Hook(const BASE_NS::string member)
184 {
185 if (obj_.IsEmpty()) {
186 return;
187 }
188 auto ValueObject = obj_.GetObject();
189
190 auto* accessor = new MemberProxy(this, member);
191 accessors_.push_back(BASE_NS::unique_ptr<MemberProxy>(accessor));
192 ValueObject.AddProperty({ accessor->Name().data(), nullptr, nullptr, MemberProxy::Getter, MemberProxy::Setter,
193 nullptr, napi_default_jsproperty, static_cast<void*>(accessor) });
194 }
195
Reset()196 void ObjectPropertyProxy::Reset()
197 {
198 napi_status status;
199 if (obj_.IsEmpty()) {
200 return;
201 }
202
203 RemoveHandlers();
204
205 NapiApi::Env env(obj_.GetEnv());
206 // unhook all hooked members.
207 auto ValueObject = obj_.GetObject();
208 for (; !accessors_.empty();) {
209 auto it(accessors_.begin());
210 // remove the property
211 ValueObject.DeleteProperty((*it)->Name());
212 // delete accessor
213 accessors_.erase(it);
214 }
215 bool exception = false;
216 status = napi_is_exception_pending(env, &exception);
217 if (exception) {
218 // if it's set, just clear it.
219 // interestingly even though the napi_*_property might have returned napi_pending_exception (or other error
220 // state) it seems that napi_is_exception_pending WILL return false though.
221 napi_value res;
222 status = napi_get_and_clear_last_exception(env, &res);
223 }
224 }
225
Destroy()226 void ObjectPropertyProxy::Destroy()
227 {
228 Reset();
229 // release ref.
230 obj_.Reset();
231 }
232
Value()233 napi_value ObjectPropertyProxy::Value()
234 {
235 return obj_.GetValue();
236 }
237
SetValue(NapiApi::FunctionContext<> & info)238 void ObjectPropertyProxy::SetValue(NapiApi::FunctionContext<>& info)
239 {
240 NapiApi::FunctionContext<NapiApi::Object> data { info };
241 if (data) {
242 SetValue(data.Arg<0>());
243 }
244 }
245
ObjectPropertyProxy(META_NS::IProperty::Ptr prop)246 ObjectPropertyProxy::ObjectPropertyProxy(META_NS::IProperty::Ptr prop) : PropertyProxy(prop) {}
247
~ObjectPropertyProxy()248 ObjectPropertyProxy::~ObjectPropertyProxy()
249 {
250 Reset();
251 }
Reset()252 void BitmapProxy::Reset()
253 {
254 Lock();
255 if (!prop_) {
256 Unlock();
257 return;
258 }
259 BASE_NS::string name = prop_->GetName();
260 RemoveHandlers();
261
262 if (!obj_.IsEmpty()) {
263 obj_.GetObject().DeleteProperty(name);
264 }
265 obj_.Reset();
266 scene_.Reset();
267 Unlock();
268 }
269
BitmapProxy(NapiApi::Object scn,NapiApi::Object obj,META_NS::Property<SCENE_NS::IBitmap::Ptr> prop)270 BitmapProxy::BitmapProxy(NapiApi::Object scn, NapiApi::Object obj, META_NS::Property<SCENE_NS::IBitmap::Ptr> prop)
271 : PropertyProxy(prop)
272 {
273 scene_ = scn;
274 obj_ = obj;
275 SyncGet();
276 }
~BitmapProxy()277 BitmapProxy::~BitmapProxy()
278 {
279 // Unhook the objects.
280 Reset();
281 }
282
UpdateLocalValues()283 void BitmapProxy::UpdateLocalValues()
284 {
285 // executed in engine thread (locks handled outside)
286 if (prop_) {
287 value = META_NS::Property<SCENE_NS::IBitmap::Ptr>(prop_)->GetValue();
288 }
289 }
UpdateRemoteValues()290 void BitmapProxy::UpdateRemoteValues()
291 {
292 // executed in engine thread (locks handled outside)
293 if (prop_) {
294 META_NS::Property<SCENE_NS::IBitmap::Ptr>(prop_)->SetValue(value);
295 }
296 }
SetValue(const SCENE_NS::IBitmap::Ptr v)297 void BitmapProxy::SetValue(const SCENE_NS::IBitmap::Ptr v)
298 {
299 Lock();
300 if (value != v) {
301 value = v;
302 ScheduleUpdate();
303 }
304 Unlock();
305 }
Value()306 napi_value BitmapProxy::Value()
307 {
308 // should be executed in the javascript thread.
309 Lock();
310 SCENE_NS::IBitmap::Ptr res = value;
311 Unlock();
312 if (auto cached = FetchJsObj(res)) {
313 return cached.ToNapiValue();
314 }
315 NapiApi::Env env(obj_.GetEnv());
316
317 if (res) {
318 NapiApi::Object parms(env);
319 napi_value args[] = {
320 scene_.GetValue(),
321 parms.ToNapiValue()
322 };
323 MakeNativeObjectParam(env, res, BASE_NS::countof(args), args);
324
325 auto size = res->Size()->GetValue();
326 BASE_NS::string uri;
327 if (auto m = interface_cast<META_NS::IMetadata>(res)) {
328 if (auto p = m->GetProperty<BASE_NS::string>("Uri")) {
329 uri = p->GetValue();
330 }
331 }
332 auto name = interface_cast<META_NS::IObject>(res)->GetName();
333 parms.Set("uri", uri);
334 NapiApi::Object imageJS(GetJSConstructor(env, "Image"), BASE_NS::countof(args), args);
335 return imageJS.ToNapiValue();
336 }
337 return env.GetNull();
338 }
SetValue(NapiApi::FunctionContext<> & info)339 void BitmapProxy::SetValue(NapiApi::FunctionContext<>& info)
340 {
341 NapiApi::FunctionContext<NapiApi::Object> data { info };
342 if (data) {
343 NapiApi::Object val = data.Arg<0>();
344 auto bitmap = GetNativeMeta<SCENE_NS::IBitmap>(val);
345 Lock();
346 value = bitmap;
347 ScheduleUpdate();
348 Unlock();
349 }
350 }
351
PropertyToProxy(NapiApi::Object scene,NapiApi::Object obj,const META_NS::IProperty::Ptr & t)352 BASE_NS::shared_ptr<PropertyProxy> PropertyToProxy(
353 NapiApi::Object scene, NapiApi::Object obj, const META_NS::IProperty::Ptr& t)
354 {
355 if (META_NS::IsCompatibleWith<float>(t)) {
356 return BASE_NS::shared_ptr { new TypeProxy<float>(obj, t) };
357 }
358 if (META_NS::IsCompatibleWith<int32_t>(t)) {
359 return BASE_NS::shared_ptr { new TypeProxy<int32_t>(obj, t) };
360 }
361 if (META_NS::IsCompatibleWith<uint32_t>(t)) {
362 return BASE_NS::shared_ptr { new TypeProxy<uint32_t>(obj, t) };
363 }
364 if (META_NS::IsCompatibleWith<int64_t>(t)) {
365 return BASE_NS::shared_ptr { new TypeProxy<int64_t>(obj, t) };
366 }
367 if (META_NS::IsCompatibleWith<uint64_t>(t)) {
368 return BASE_NS::shared_ptr { new TypeProxy<uint64_t>(obj, t) };
369 }
370 if (META_NS::IsCompatibleWith<BASE_NS::Math::Vec2>(t)) {
371 return BASE_NS::shared_ptr { new Vec2Proxy(obj.GetEnv(), t) };
372 }
373 if (META_NS::IsCompatibleWith<BASE_NS::Math::Vec3>(t)) {
374 return BASE_NS::shared_ptr { new Vec3Proxy(obj.GetEnv(), t) };
375 }
376 if (META_NS::IsCompatibleWith<BASE_NS::Math::Vec4>(t)) {
377 return BASE_NS::shared_ptr { new Vec4Proxy(obj.GetEnv(), t) };
378 }
379 if (META_NS::IsCompatibleWith<SCENE_NS::IBitmap::Ptr>(t)) {
380 return BASE_NS::shared_ptr { new BitmapProxy(scene, obj, t) };
381 }
382 auto any = META_NS::GetInternalAny(t);
383 LOG_F("Unsupported property type [%s]", any ? any->GetTypeIdString().c_str() : "<Unknown>");
384 return nullptr;
385 }
386
DummyFunc(napi_env e,napi_callback_info i)387 static napi_value DummyFunc(napi_env e, napi_callback_info i)
388 {
389 NapiApi::FunctionContext<> info(e, i);
390 return info.GetUndefined();
391 };
PropProxGet(napi_env e,napi_callback_info i)392 static napi_value PropProxGet(napi_env e, napi_callback_info i)
393 {
394 NapiApi::FunctionContext<> info(e, i);
395 auto pc = static_cast<PropertyProxy*>(info.GetData());
396 if (pc) {
397 return pc->Value();
398 }
399 return info.GetUndefined();
400 };
PropProxSet(napi_env e,napi_callback_info i)401 static napi_value PropProxSet(napi_env e, napi_callback_info i)
402 {
403 NapiApi::FunctionContext<>info (e, i);
404 auto pc = static_cast<PropertyProxy*>(info.GetData());
405 if (pc) {
406 pc->SetValue(info);
407 }
408 return info.GetUndefined();
409 };
CreateProxyDesc(const char * name,BASE_NS::shared_ptr<PropertyProxy> proxy)410 napi_property_descriptor CreateProxyDesc(const char* name, BASE_NS::shared_ptr<PropertyProxy> proxy)
411 {
412 napi_property_descriptor desc { name, nullptr, nullptr, nullptr, nullptr, nullptr, napi_default_jsproperty,
413 static_cast<void*>(proxy.get()) };
414 if (proxy) {
415 desc.getter = PropProxGet;
416 desc.setter = PropProxSet;
417 } else {
418 desc.getter = desc.setter = DummyFunc;
419 }
420 return desc;
421 }
422