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 "SceneJS.h"
17
18 #include "LightJS.h"
19 #include "MaterialJS.h"
20 #include "MeshResourceJS.h"
21 #include "NodeJS.h"
22 #include "PromiseBase.h"
23 static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" };
24
25 #include <meta/api/make_callback.h>
26 #include <meta/interface/animation/intf_animation.h>
27 #include <meta/interface/intf_object_context.h>
28 #include <meta/interface/intf_task_queue.h>
29 #include <meta/interface/intf_task_queue_registry.h>
30 #include <meta/interface/property/construct_property.h>
31 #include <meta/interface/property/property_events.h>
32 #include <scene/ext/intf_render_resource.h>
33 #include <scene/interface/intf_material.h>
34 #include <scene/interface/intf_mesh_resource.h>
35 #include <scene/interface/intf_node_import.h>
36 #include <scene/interface/intf_render_configuration.h>
37 #include <scene/interface/intf_scene.h>
38 #include <scene/interface/intf_scene_manager.h>
39
40 #include <core/image/intf_image_loader_manager.h>
41 #include <core/intf_engine.h>
42 #include <render/device/intf_gpu_resource_manager.h>
43 #include <render/intf_render_context.h>
44
45 #ifdef __SCENE_ADAPTER__
46 #include <parameters.h>
47 #include "3d_widget_adapter_log.h"
48 #include "scene_adapter/scene_adapter.h"
49 #endif
50
51 // LEGACY COMPATIBILITY start
52 #include <geometry_definition/GeometryDefinition.h>
53 #include <scene/ext/intf_ecs_context.h>
54 #include <scene/ext/intf_ecs_object.h>
55 #include <scene/ext/intf_ecs_object_access.h>
56
57 #include <3d/ecs/components/name_component.h>
58 #include <3d/ecs/components/node_component.h>
59 // fix names to match "ye olde" implementation
60 // the bug that unnamed nodes stops hierarchy creation also still exists, works around that issue too.
Fixnames(SCENE_NS::IScene::Ptr scene)61 void Fixnames(SCENE_NS::IScene::Ptr scene)
62 {
63 struct rr {
64 uint32_t id_ = 1;
65 // not actual tree, but map of entities, and their children.
66 BASE_NS::unordered_map<CORE_NS::Entity, BASE_NS::vector<CORE_NS::Entity>> tree;
67 BASE_NS::vector<CORE_NS::Entity> roots;
68 CORE3D_NS::INodeComponentManager* cm;
69 CORE3D_NS::INameComponentManager* nm;
70 explicit rr(SCENE_NS::IScene::Ptr scene)
71 {
72 CORE_NS::IEcs::Ptr ecs = scene->GetInternalScene()->GetEcsContext().GetNativeEcs();
73 cm = CORE_NS::GetManager<CORE3D_NS::INodeComponentManager>(*ecs);
74 nm = CORE_NS::GetManager<CORE3D_NS::INameComponentManager>(*ecs);
75 fix();
76 }
77 void scan()
78 {
79 const auto count = cm->GetComponentCount();
80 // collect nodes and their children.
81 tree.reserve(cm->GetComponentCount());
82 for (auto i = 0; i < count; i++) {
83 auto enti = cm->GetEntity(i);
84 // add node to our list. (if not yet added)
85 tree.insert({ enti });
86 auto parent = cm->Get(i).parent;
87 if (CORE_NS::EntityUtil::IsValid(parent)) {
88 tree[parent].push_back(enti);
89 } else {
90 // no parent, so it's a "root"
91 roots.push_back(enti);
92 }
93 }
94 }
95 void recurse(CORE_NS::Entity id)
96 {
97 CORE3D_NS::NameComponent c = nm->Get(id);
98 if (c.name.empty()) {
99 // create a name for unnamed node.
100 c.name = "Unnamed Node ";
101 c.name += BASE_NS::to_string(id_++);
102 nm->Set(id, c);
103 }
104 for (auto c : tree[id]) {
105 recurse(c);
106 }
107 }
108 void fix()
109 {
110 scan();
111 for (auto i : roots) {
112 id_ = 1;
113 // force root node name to match legacy by default.
114 for (auto c : tree[i]) {
115 recurse(c);
116 }
117 }
118 }
119 } r(scene);
120 }
121 // LEGACY COMPATIBILITY end
122
123 using IntfPtr = BASE_NS::shared_ptr<CORE_NS::IInterface>;
124 using IntfWeakPtr = BASE_NS::weak_ptr<CORE_NS::IInterface>;
125
Init(napi_env env,napi_value exports)126 void SceneJS::Init(napi_env env, napi_value exports)
127 {
128 using namespace NapiApi;
129 // clang-format off
130 auto loadFun = [](napi_env e, napi_callback_info cb) -> napi_value {
131 FunctionContext<> fc(e, cb);
132 return SceneJS::Load(fc);
133 };
134
135 napi_property_descriptor props[] = {
136 // static methods
137 napi_property_descriptor{ "load", nullptr, loadFun, nullptr, nullptr, nullptr,
138 (napi_property_attributes)(napi_static|napi_default_method)},
139 // properties
140 GetSetProperty<uint32_t, SceneJS, &SceneJS::GetRenderMode, &SceneJS::SetRenderMode>("renderMode"),
141 GetSetProperty<NapiApi::Object, SceneJS, &SceneJS::GetEnvironment, &SceneJS::SetEnvironment>("environment"),
142 GetProperty<NapiApi::Array, SceneJS, &SceneJS::GetAnimations>("animations"),
143 // animations
144 GetProperty<BASE_NS::string, SceneJS, &SceneJS::GetRoot>("root"),
145 // scene methods
146 Method<NapiApi::FunctionContext<BASE_NS::string>, SceneJS, &SceneJS::GetNode>("getNodeByPath"),
147 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::GetResourceFactory>("getResourceFactory"),
148 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::Dispose>("destroy"),
149
150 // SceneResourceFactory methods
151 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateCamera>("createCamera"),
152 Method<NapiApi::FunctionContext<NapiApi::Object, uint32_t>, SceneJS, &SceneJS::CreateLight>("createLight"),
153 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateNode>("createNode"),
154 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateTextNode>("createTextNode"),
155 Method<NapiApi::FunctionContext<NapiApi::Object, uint32_t>,
156 SceneJS, &SceneJS::CreateMaterial>("createMaterial"),
157 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateShader>("createShader"),
158 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateImage>("createImage"),
159 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateEnvironment>("createEnvironment"),
160 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::CreateScene>("createScene"),
161
162 Method<NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>, SceneJS,
163 &SceneJS::ImportNode>("importNode"),
164 Method<NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>, SceneJS,
165 &SceneJS::ImportScene>("importScene"),
166 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::RenderFrame>("renderFrame"),
167
168 Method<FunctionContext<Object, Object>, SceneJS, &SceneJS::CreateMeshResource>("createMesh"),
169 Method<FunctionContext<Object, Object>, SceneJS, &SceneJS::CreateGeometry>("createGeometry"),
170 };
171 // clang-format on
172
173 napi_value func;
174 auto status = napi_define_class(env, "Scene", NAPI_AUTO_LENGTH, BaseObject::ctor<SceneJS>(), nullptr,
175 sizeof(props) / sizeof(props[0]), props, &func);
176
177 napi_set_named_property(env, exports, "Scene", func);
178
179 NapiApi::MyInstanceState* mis;
180 GetInstanceData(env, reinterpret_cast<void**>(&mis));
181 mis->StoreCtor("Scene", func);
182 }
183
RegisterEnums(NapiApi::Object exports)184 void SceneJS::RegisterEnums(NapiApi::Object exports)
185 {
186 napi_value v;
187 NapiApi::Object en(exports.GetEnv());
188
189 napi_create_uint32(en.GetEnv(), static_cast<uint32_t>(SCENE_NS::RenderMode::IF_DIRTY), &v);
190 en.Set("RENDER_WHEN_DIRTY", v);
191 napi_create_uint32(en.GetEnv(), static_cast<uint32_t>(SCENE_NS::RenderMode::ALWAYS), &v);
192 en.Set("RENDER_CONTINUOUSLY", v);
193 napi_create_uint32(en.GetEnv(), static_cast<uint32_t>(SCENE_NS::RenderMode::MANUAL), &v);
194 en.Set("RENDER_MANUALLY", v);
195
196 exports.Set("RenderMode", en);
197 }
198
199
FetchResourceOrUri(napi_env e,napi_value arg)200 BASE_NS::string FetchResourceOrUri(napi_env e, napi_value arg)
201 {
202 napi_valuetype type;
203 napi_typeof(e, arg, &type);
204 if (type == napi_string) {
205 BASE_NS::string uri = NapiApi::Value<BASE_NS::string>(e, arg);
206 // check if there is a protocol
207 auto t = uri.find("://");
208 if (t == BASE_NS::string::npos) {
209 // no proto . so use default
210 // set system file as default format
211 uri.insert(0, "file://");
212 }
213 return uri;
214 }
215 if (type == napi_object) {
216 NapiApi::Object resource(e, arg);
217 uint32_t id = resource.Get<uint32_t>("id");
218 uint32_t resourceType = resource.Get<uint32_t>("type");
219 NapiApi::Array parms = resource.Get<NapiApi::Array>("params");
220 BASE_NS::string uri;
221 if ((id == 0) && (resourceType == 30000)) { // 30000: param
222 // seems like a correct rawfile.
223 uri = parms.Get<BASE_NS::string>(0);
224 }
225 if (!uri.empty()) {
226 // add the schema then
227 uri.insert(0, "OhosRawFile://");
228 }
229 return uri;
230 }
231 return "";
232 }
FetchResourceOrUri(const NapiApi::Object obj)233 BASE_NS::string FetchResourceOrUri(const NapiApi::Object obj)
234 {
235 return FetchResourceOrUri(obj.GetEnv(), obj.ToNapiValue());
236 }
237
FetchResourceOrUri(NapiApi::FunctionContext<> & ctx)238 BASE_NS::string FetchResourceOrUri(NapiApi::FunctionContext<>& ctx)
239 {
240 BASE_NS::string uri;
241 NapiApi::FunctionContext<NapiApi::Object> resourceContext(ctx);
242 NapiApi::FunctionContext<BASE_NS::string> uriContext(ctx);
243
244 if (uriContext) {
245 // actually not supported anymore.
246 uri = uriContext.Arg<0>();
247 // check if there is a protocol
248 auto t = uri.find("://");
249 if (t == BASE_NS::string::npos) {
250 // no proto . so use default
251 uri.insert(0, "file://");
252 }
253 } else if (resourceContext) {
254 // get it from resource then
255 NapiApi::Object resource = resourceContext.Arg<0>();
256 uint32_t id = resource.Get<uint32_t>("id");
257 uint32_t type = resource.Get<uint32_t>("type");
258 NapiApi::Array parms = resource.Get<NapiApi::Array>("params");
259 // try to identify a $rawfile. (from resource object)
260 if ((id == 0) && (type == 30000)) { // 30000: param
261 // seems like a correct rawfile.
262 uri = parms.Get<BASE_NS::string>(0);
263 }
264 if (!uri.empty()) {
265 // add the schema then
266 uri.insert(0, "OhosRawFile://");
267 }
268 }
269 return uri;
270 }
271
Load(NapiApi::FunctionContext<> & ctx)272 napi_value SceneJS::Load(NapiApi::FunctionContext<>& ctx)
273 {
274 BASE_NS::string uri = FetchResourceOrUri(ctx);
275 if (uri.empty()) {
276 uri = "scene://empty";
277 }
278 // make sure slashes are correct.. *eh*
279 for (;;) {
280 auto t = uri.find_first_of('\\');
281 if (t == BASE_NS::string::npos) {
282 break;
283 }
284 uri[t] = '/';
285 }
286
287 struct Promise : public PromiseBase {
288 using PromiseBase::PromiseBase;
289 BASE_NS::string uri;
290 SCENE_NS::IScene::Ptr scene;
291 bool SetResult() override
292 {
293 if (scene) {
294 auto obj = interface_pointer_cast<META_NS::IObject>(scene);
295 auto jsscene = CreateJsObj(env_, "Scene", obj, true, 0, nullptr);
296 // link the native object to js object.
297 StoreJsObj(obj, jsscene);
298 result_ = jsscene.ToNapiValue();
299
300 auto curenv = jsscene.Get<NapiApi::Object>("environment");
301 if (curenv.IsUndefinedOrNull()) {
302 // setup default env
303 NapiApi::Object argsIn(env_);
304 argsIn.Set("name", "DefaultEnv");
305
306 auto* tro = static_cast<SceneJS*>(jsscene.Native<TrueRootObject>());
307 if (tro == nullptr) {
308 LOG_E("tro is nullptr");
309 return false;
310 }
311 auto res = tro->CreateEnvironment(jsscene, argsIn);
312 res.Set("backgroundType", NapiApi::Value<uint32_t>(env_, 1)); // image.. but with null.
313 jsscene.Set("environment", res);
314 }
315 for (auto&& c : scene->GetCameras().GetResult()) {
316 c->RenderingPipeline()->SetValue(SCENE_NS::CameraPipeline::FORWARD);
317 c->ColorTargetCustomization()->SetValue(
318 { SCENE_NS::ColorFormat{BASE_NS::BASE_FORMAT_R16G16B16A16_SFLOAT}});
319 }
320
321 #ifdef __SCENE_ADAPTER__
322 // set SceneAdapter
323 auto oo = GetRootObject(NapiApi::Object(env_, result_));
324 if (oo == nullptr) {
325 CORE_LOG_E("GetRootObject return nullptr in SceneJS Load.");
326 return false;
327 }
328
329 auto sceneAdapter = std::make_shared<OHOS::Render3D::SceneAdapter>();
330 sceneAdapter->SetSceneObj(oo->GetNativeObject());
331 auto sceneJs = static_cast<SceneJS*>(oo);
332 sceneJs->scene_ = sceneAdapter;
333 #endif
334 return true;
335 }
336 return false;
337 };
338 };
339 Promise* promise = new Promise(ctx.Env());
340 promise->uri = uri;
341
342 auto jsPromise = promise->ToNapiValue();
343
344 auto params = interface_pointer_cast<META_NS::IMetadata>(META_NS::GetObjectRegistry().GetDefaultObjectContext());
345 auto m = META_NS::GetObjectRegistry().Create<SCENE_NS::ISceneManager>(SCENE_NS::ClassId::SceneManager, params);
346 if (m) {
347 m->CreateScene(uri).Then(
348 [promise](SCENE_NS::IScene::Ptr scene) {
349 if (scene) {
350 promise->scene = scene;
351 auto& obr = META_NS::GetObjectRegistry();
352 // make sure we have renderconfig
353 auto rc = scene->RenderConfiguration()->GetValue();
354 if (!rc) {
355 LOG_F("No render config");
356 }
357 // Make sure there's a valid root node
358 scene->GetInternalScene()->GetEcsContext().CreateUnnamedRootNode();
359 // LEGACY COMPATIBILITY start
360 Fixnames(scene);
361 // LEGACY COMPATIBILITY end
362 AddScene(&obr, scene);
363 }
364 promise->SettleLater();
365 },
366 nullptr);
367 } else {
368 promise->Reject();
369 }
370 return jsPromise;
371 }
372
RenderFrame(NapiApi::FunctionContext<> & ctx)373 napi_value SceneJS::RenderFrame(NapiApi::FunctionContext<>& ctx)
374 {
375 if (ctx.ArgCount() > 1) {
376 CORE_LOG_E("render frame %d", __LINE__);
377 return ctx.GetUndefined();
378 }
379 bool res = true;
380 #ifdef __SCENE_ADAPTER__
381 auto sceneAdapter = std::static_pointer_cast<OHOS::Render3D::SceneAdapter>(scene_);
382 if (sceneAdapter) {
383 sceneAdapter->SetNeedsRepaint(false);
384 sceneAdapter->RenderFrame(false);
385 }
386 #endif
387 return ctx.GetBoolean(res);
388 }
389
Dispose(NapiApi::FunctionContext<> & ctx)390 napi_value SceneJS::Dispose(NapiApi::FunctionContext<>& ctx)
391 {
392 DisposeNative(nullptr);
393 #ifdef __SCENE_ADAPTER__
394 if (scene_) {
395 scene_->Deinit();
396 }
397 #endif
398 return {};
399 }
DisposeNative(void *)400 void SceneJS::DisposeNative(void*)
401 {
402 if (!disposed_) {
403 disposed_ = true;
404 LOG_V("SCENE_JS::DisposeNative");
405
406 NapiApi::Object scen(env_);
407 napi_value tmp;
408 napi_create_external(
409 env_, static_cast<void*>(this),
410 [](napi_env env, void* data, void* finalize_hint) {
411 // do nothing.
412 },
413 nullptr, &tmp);
414 scen.Set("SceneJS", tmp);
415 napi_value scene = scen.ToNapiValue();
416
417 // dispose active environment
418 if (auto env = environmentJS_.GetObject()) {
419 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
420 if (func) {
421 func.Invoke(env, 1, &scene);
422 }
423 }
424 environmentJS_.Reset();
425
426 // dispose all cameras/env/etcs.
427 while (!strongDisposables_.empty()) {
428 auto it = strongDisposables_.begin();
429 auto token = it->first;
430 auto env = it->second.GetObject();
431 if (env) {
432 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
433 if (func) {
434 func.Invoke(env, 1, &scene);
435 }
436 } else {
437 strongDisposables_.erase(strongDisposables_.begin());
438 }
439 }
440
441 // dispose
442 while (!disposables_.empty()) {
443 auto env = disposables_.begin()->second.GetObject();
444 if (env) {
445 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
446 if (func) {
447 func.Invoke(env, 1, &scene);
448 }
449 } else {
450 disposables_.erase(disposables_.begin());
451 }
452 }
453
454 for (auto b : bitmaps_) {
455 b.second.reset();
456 }
457 if (auto nativeScene = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject())) {
458 // reset the native object refs
459 SetNativeObject(nullptr, false);
460 SetNativeObject(nullptr, true);
461
462 auto r = nativeScene->RenderConfiguration()->GetValue();
463 if (r) {
464 r->Environment()->SetValue(nullptr);
465 r.reset();
466 }
467 FlushScenes();
468 }
469 }
470 }
GetInstanceImpl(uint32_t id)471 void* SceneJS::GetInstanceImpl(uint32_t id)
472 {
473 if (id == SceneJS::ID)
474 return this;
475 return nullptr;
476 }
Finalize(napi_env env)477 void SceneJS::Finalize(napi_env env)
478 {
479 DisposeNative(nullptr);
480 BaseObject<SceneJS>::Finalize(env);
481 }
482
AddScene(META_NS::IObjectRegistry * obr,SCENE_NS::IScene::Ptr scene)483 void SceneJS::AddScene(META_NS::IObjectRegistry* obr, SCENE_NS::IScene::Ptr scene)
484 {
485 if (!obr) {
486 return;
487 }
488 auto params = interface_pointer_cast<META_NS::IMetadata>(obr->GetDefaultObjectContext());
489 if (!params) {
490 return;
491 }
492 auto duh = params->GetArrayProperty<IntfWeakPtr>("Scenes");
493 if (!duh) {
494 return;
495 }
496 duh->AddValue(interface_pointer_cast<CORE_NS::IInterface>(scene));
497 }
498
SceneJS(napi_env e,napi_callback_info i)499 SceneJS::SceneJS(napi_env e, napi_callback_info i) : BaseObject<SceneJS>(e, i)
500 {
501 LOG_V("SceneJS ++");
502 env_ = e; // store..
503 NapiApi::FunctionContext<NapiApi::Object> fromJs(e, i);
504 if (!fromJs) {
505 // okay internal create. we will receive the object after.
506 return;
507 }
508 // SceneJS should be created by "Scene.Load" only.
509 // so this never happens.
510 }
511
FlushScenes()512 void SceneJS::FlushScenes()
513 {
514 ExecSyncTask([]() {
515 auto& obr = META_NS::GetObjectRegistry();
516 if (auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext())) {
517 if (auto duh = params->GetArrayProperty<IntfWeakPtr>("Scenes")) {
518 for (auto i = 0; i < duh->GetSize();) {
519 auto w = duh->GetValueAt(i);
520 if (w.lock() == nullptr) {
521 duh->RemoveAt(i);
522 } else {
523 i++;
524 }
525 }
526 }
527 }
528 return META_NS::IAny::Ptr {};
529 });
530 }
~SceneJS()531 SceneJS::~SceneJS()
532 {
533 LOG_V("SceneJS --");
534 DisposeNative(nullptr);
535 // flush all null scene objects here too.
536 FlushScenes();
537 if (!GetNativeObject()) {
538 return;
539 }
540 }
541
GetNode(NapiApi::FunctionContext<BASE_NS::string> & ctx)542 napi_value SceneJS::GetNode(NapiApi::FunctionContext<BASE_NS::string>& ctx)
543 {
544 // verify that path starts from "correct root" and then let the root node handle the rest.
545 NapiApi::Object meJs(ctx.This());
546 NapiApi::Object root = meJs.Get<NapiApi::Object>("root");
547 BASE_NS::string rootName = root.Get<BASE_NS::string>("name");
548 NapiApi::Function func = root.Get<NapiApi::Function>("getNodeByPath");
549 BASE_NS::string path = ctx.Arg<0>();
550 if (path.empty() || (path == BASE_NS::string_view("/")) || (path==rootName)) {
551 // empty or '/' or "exact rootnodename". so return root
552 return root.ToNapiValue();
553 }
554
555 // remove the "root nodes name", if given (make sure it also matches though..)
556 auto pos = 0;
557 if (path[0] != '/') {
558 pos = path.find('/', 0);
559 BASE_NS::string_view step = path.substr(0, pos);
560 if (!step.empty() && (step != rootName)) {
561 // root not matching
562 return ctx.GetNull();
563 }
564 }
565 if (pos != BASE_NS::string_view::npos) {
566 path = path.substr(pos + 1);
567 }
568
569 if (path.empty()) {
570 // after removing the root node name
571 // nothing left in path, so return root.
572 return root.ToNapiValue();
573 }
574
575 napi_value newpath = ctx.GetString(path);
576 if (newpath) {
577 return func.Invoke(root, 1, &newpath);
578 }
579 return ctx.GetNull();
580 }
GetRoot(NapiApi::FunctionContext<> & ctx)581 napi_value SceneJS::GetRoot(NapiApi::FunctionContext<>& ctx)
582 {
583 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
584 SCENE_NS::INode::Ptr root = scene->GetRootNode().GetResult();
585 auto obj = interface_pointer_cast<META_NS::IObject>(root);
586
587 if (auto cached = FetchJsObj(obj)) {
588 // always return the same js object.
589 return cached.ToNapiValue();
590 }
591
592 NapiApi::StrongRef sceneRef { ctx.This() };
593 if (!GetNativeMeta<SCENE_NS::IScene>(sceneRef.GetObject())) {
594 LOG_F("INVALID SCENE!");
595 }
596
597 NapiApi::Object argJS(ctx.GetEnv());
598 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
599
600 auto js = CreateFromNativeInstance(
601 ctx.GetEnv(), obj, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
602 if (auto nm = GetJsWrapper<NodeImpl>(js)) {
603 nm->Attached(true);
604 }
605 return js.ToNapiValue();
606 }
607 return ctx.GetUndefined();
608 }
609
GetEnvironment(NapiApi::FunctionContext<> & ctx)610 napi_value SceneJS::GetEnvironment(NapiApi::FunctionContext<>& ctx)
611 {
612 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
613 SCENE_NS::IEnvironment::Ptr environment;
614 auto rc = scene->RenderConfiguration()->GetValue();
615 if (rc) {
616 environment = rc->Environment()->GetValue();
617 }
618 if (environment) {
619 auto obj = interface_pointer_cast<META_NS::IObject>(environment);
620 if (auto cached = FetchJsObj(obj)) {
621 // always return the same js object.
622 return cached.ToNapiValue();
623 }
624
625 NapiApi::StrongRef sceneRef { ctx.This() };
626 if (!GetNativeMeta<SCENE_NS::IScene>(sceneRef.GetObject())) {
627 LOG_F("INVALID SCENE!");
628 }
629
630 NapiApi::Env env(ctx.Env());
631 NapiApi::Object argJS(env);
632 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
633
634 environmentJS_ =
635 NapiApi::StrongRef(CreateFromNativeInstance(env, obj, false, BASE_NS::countof(args), args));
636 return environmentJS_.GetValue();
637 }
638 }
639 return ctx.GetNull();
640 }
641
SetEnvironment(NapiApi::FunctionContext<NapiApi::Object> & ctx)642 void SceneJS::SetEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
643 {
644 NapiApi::Object envObj = ctx.Arg<0>();
645 if (!envObj) {
646 return;
647 }
648 if (auto currentlySet = environmentJS_.GetObject()) {
649 if (envObj.StrictEqual(currentlySet)) { // setting the exactly the same environment. do nothing.
650 return;
651 }
652 }
653 environmentJS_ = NapiApi::StrongRef(envObj);
654
655 SCENE_NS::IEnvironment::Ptr environment = GetNativeMeta<SCENE_NS::IEnvironment>(envObj);
656
657 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
658 auto rc = scene->RenderConfiguration()->GetValue();
659 if (rc) {
660 rc->Environment()->SetValue(environment);
661 }
662 }
663 }
664
665 // resource factory
666
GetResourceFactory(NapiApi::FunctionContext<> & ctx)667 napi_value SceneJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx)
668 {
669 // just return this. as scene is the factory also.
670 return ctx.This().ToNapiValue();
671 }
CreateEnvironment(NapiApi::Object scene,NapiApi::Object argsIn)672 NapiApi::Object SceneJS::CreateEnvironment(NapiApi::Object scene, NapiApi::Object argsIn)
673 {
674 napi_env env = scene.GetEnv();
675 napi_value args[] = { scene.ToNapiValue(), argsIn.ToNapiValue() };
676 return NapiApi::Object(GetJSConstructor(env, "Environment"), BASE_NS::countof(args), args);
677 }
CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object> & ctx)678 napi_value SceneJS::CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
679 {
680 struct Promise : public PromiseBase {
681 using PromiseBase::PromiseBase;
682 NapiApi::StrongRef this_;
683 NapiApi::StrongRef args_;
684 bool SetResult() override
685 {
686 auto* tro = static_cast<SceneJS*>(this_.GetObject().Native<TrueRootObject>());
687 if (tro == nullptr) {
688 LOG_E("tro is nullptr");
689 return false;
690 }
691 result_ = tro->CreateEnvironment(this_.GetObject(), args_.GetObject()).ToNapiValue();
692 return true;
693 };
694 };
695 Promise* promise = new Promise(ctx.Env());
696 auto jsPromise = promise->ToNapiValue();
697 promise->this_ = NapiApi::StrongRef(ctx.This());
698 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
699
700 promise->SettleNow();
701 return jsPromise;
702 }
703
CreateCamera(NapiApi::FunctionContext<NapiApi::Object> & ctx)704 napi_value SceneJS::CreateCamera(NapiApi::FunctionContext<NapiApi::Object>& ctx)
705 {
706 struct Promise : public PromiseBase {
707 using PromiseBase::PromiseBase;
708 NapiApi::StrongRef this_;
709 NapiApi::StrongRef args_;
710 bool SetResult() override
711 {
712 napi_value args[] = {
713 this_.GetValue(), // scene..
714 args_.GetValue() // params.
715 };
716 result_ = NapiApi::Object(GetJSConstructor(env_, "Camera"), BASE_NS::countof(args), args).ToNapiValue();
717 return true;
718 };
719 };
720 Promise* promise = new Promise(ctx.Env());
721 auto jsPromise = promise->ToNapiValue();
722 promise->this_ = NapiApi::StrongRef(ctx.This());
723 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
724
725 promise->SettleNow();
726 return jsPromise;
727 }
728
CreateLight(NapiApi::FunctionContext<NapiApi::Object,uint32_t> & ctx)729 napi_value SceneJS::CreateLight(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
730 {
731 struct Promise : public PromiseBase {
732 using PromiseBase::PromiseBase;
733 NapiApi::StrongRef this_;
734 NapiApi::StrongRef args_;
735 uint32_t lightType_;
736 bool SetResult() override
737 {
738 napi_value args[] = {
739 this_.GetValue(), // scene..
740 args_.GetValue() // params.
741 };
742 NapiApi::Function func;
743 switch (lightType_) {
744 case BaseLight::DIRECTIONAL: {
745 func = GetJSConstructor(env_, "DirectionalLight");
746 break;
747 }
748 case BaseLight::POINT: {
749 func = GetJSConstructor(env_, "PointLight");
750 break;
751 }
752 case BaseLight::SPOT: {
753 func = GetJSConstructor(env_, "SpotLight");
754 break;
755 }
756 default:
757 break;
758 }
759 if (!func) {
760 return false;
761 }
762 result_ = NapiApi::Object(func, BASE_NS::countof(args), args).ToNapiValue();
763 return true;
764 };
765 };
766 Promise* promise = new Promise(ctx.Env());
767 auto jsPromise = promise->ToNapiValue();
768 promise->this_ = NapiApi::StrongRef(ctx.This());
769 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
770 promise->lightType_ = ctx.Arg<1>();
771
772 promise->SettleNow();
773 return jsPromise;
774 }
775
CreateNode(NapiApi::FunctionContext<NapiApi::Object> & ctx)776 napi_value SceneJS::CreateNode(NapiApi::FunctionContext<NapiApi::Object>& ctx)
777 {
778 struct Promise : public PromiseBase {
779 using PromiseBase::PromiseBase;
780 NapiApi::StrongRef this_;
781 NapiApi::StrongRef args_;
782 bool SetResult() override
783 {
784 napi_value args[] = {
785 this_.GetValue(), // scene..
786 args_.GetValue() // params.
787 };
788 result_ = NapiApi::Object(GetJSConstructor(env_, "Node"), BASE_NS::countof(args), args).ToNapiValue();
789 return true;
790 };
791 };
792 Promise* promise = new Promise(ctx.Env());
793 auto jsPromise = promise->ToNapiValue();
794 promise->this_ = NapiApi::StrongRef(ctx.This());
795 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
796
797 promise->SettleNow();
798 return jsPromise;
799 }
800
CreateTextNode(NapiApi::FunctionContext<NapiApi::Object> & ctx)801 napi_value SceneJS::CreateTextNode(NapiApi::FunctionContext<NapiApi::Object>& ctx)
802 {
803 struct Promise : public PromiseBase {
804 using PromiseBase::PromiseBase;
805 NapiApi::StrongRef this_;
806 NapiApi::StrongRef args_;
807 bool SetResult() override
808 {
809 napi_value args[] = {
810 this_.GetValue(), // scene..
811 args_.GetValue() // params.
812 };
813 result_ = NapiApi::Object(GetJSConstructor(env_, "TextNode"), BASE_NS::countof(args), args).ToNapiValue();
814 return true;
815 };
816 };
817 Promise* promise = new Promise(ctx.Env());
818 auto jsPromise = promise->ToNapiValue();
819 promise->this_ = NapiApi::StrongRef(ctx.This());
820 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
821
822 promise->SettleNow();
823 return jsPromise;
824 }
825
CreateMaterial(NapiApi::FunctionContext<NapiApi::Object,uint32_t> & ctx)826 napi_value SceneJS::CreateMaterial(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
827 {
828 struct Promise : public PromiseBase {
829 using PromiseBase::PromiseBase;
830 NapiApi::StrongRef this_;
831 NapiApi::StrongRef args_;
832 uint32_t type_;
833 BASE_NS::string name_;
834 SCENE_NS::IMaterial::Ptr material_;
835 SCENE_NS::IScene::Ptr scene_;
836 bool SetResult() override
837 {
838 napi_value args[] = {
839 this_.GetValue(), // scene..
840 args_.GetValue() // params.
841 };
842
843 if (type_ == BaseMaterial::SHADER) {
844 META_NS::SetValue(material_->Type(), SCENE_NS::MaterialType::CUSTOM);
845 }
846
847 MakeNativeObjectParam(env_, material_, BASE_NS::countof(args), args);
848 NapiApi::Object materialJS(GetJSConstructor(env_, "Material"), BASE_NS::countof(args), args);
849 result_ = materialJS.ToNapiValue();
850
851 return true;
852 };
853 };
854 Promise* promise = new Promise(ctx.Env());
855 auto jsPromise = promise->ToNapiValue();
856 promise->this_ = NapiApi::StrongRef(ctx.This());
857 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
858 promise->type_ = ctx.Arg<1>();
859 NapiApi::Object parms = ctx.Arg<0>();
860 if (parms) {
861 promise->name_ = parms.Get<BASE_NS::string>("name");
862 }
863
864 promise->scene_ = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
865 if (!promise->scene_) {
866 CORE_LOG_E("promise->scene_ is null.");
867 promise->Reject();
868 return jsPromise;
869 }
870
871 // create an engine task and complete it there..
872 auto fun = [promise]() {
873 promise->material_ =
874 promise->scene_->CreateObject<SCENE_NS::IMaterial>(SCENE_NS::ClassId::Material).GetResult();
875 promise->SettleLater();
876 return false;
877 };
878
879 META_NS::GetTaskQueueRegistry()
880 .GetTaskQueue(ENGINE_THREAD)
881 ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
882
883 return jsPromise;
884 }
885
CreateScene(NapiApi::FunctionContext<> & ctx)886 napi_value SceneJS::CreateScene(NapiApi::FunctionContext<>& ctx)
887 {
888 return Load(ctx);
889 }
890
ImportNode(NapiApi::FunctionContext<BASE_NS::string,NapiApi::Object,NapiApi::Object> & ctx)891 napi_value SceneJS::ImportNode(NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>& ctx)
892 {
893 BASE_NS::string name = ctx.Arg<0>();
894 NapiApi::Object nnode = ctx.Arg<1>();
895 NapiApi::Object nparent = ctx.Arg<2>();
896 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
897 if (!nnode || !scene) {
898 return ctx.GetNull();
899 }
900 SCENE_NS::INode::Ptr node = GetNativeMeta<SCENE_NS::INode>(nnode);
901 SCENE_NS::INode::Ptr parent;
902 if (nparent) {
903 parent = GetNativeMeta<SCENE_NS::INode>(nparent);
904 } else {
905 parent = scene->GetRootNode().GetResult();
906 }
907
908 if (auto import = interface_cast<SCENE_NS::INodeImport>(parent)) {
909 auto result = import->ImportChild(node).GetResult();
910 if (!name.empty()) {
911 result->SetName(name).Wait();
912 }
913 auto obj = interface_pointer_cast<META_NS::IObject>(result);
914 if (auto cached = FetchJsObj(obj)) {
915 // always return the same js object.
916 return cached.ToNapiValue();
917 }
918
919 NapiApi::StrongRef sceneRef { ctx.This() };
920 NapiApi::Object argJS(ctx.GetEnv());
921 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
922
923 return CreateFromNativeInstance(
924 ctx.GetEnv(), obj, false /*these are owned by the scene*/, BASE_NS::countof(args), args)
925 .ToNapiValue();
926 }
927 return ctx.GetNull();
928 }
929
ImportScene(NapiApi::FunctionContext<BASE_NS::string,NapiApi::Object,NapiApi::Object> & ctx)930 napi_value SceneJS::ImportScene(NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>& ctx)
931 {
932 BASE_NS::string name = ctx.Arg<0>();
933 NapiApi::Object nextScene = ctx.Arg<1>();
934 NapiApi::Object nparent = ctx.Arg<2>();
935 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
936 if (!nextScene || !scene) {
937 return ctx.GetNull();
938 }
939 SCENE_NS::IScene::Ptr extScene = GetNativeMeta<SCENE_NS::IScene>(nextScene);
940 SCENE_NS::INode::Ptr parent;
941 if (nparent) {
942 parent = GetNativeMeta<SCENE_NS::INode>(nparent);
943 } else {
944 parent = scene->GetRootNode().GetResult();
945 }
946
947 if (auto import = interface_cast<SCENE_NS::INodeImport>(parent)) {
948 auto result = import->ImportChildScene(extScene, name).GetResult();
949 auto obj = interface_pointer_cast<META_NS::IObject>(result);
950 if (auto cached = FetchJsObj(obj)) {
951 // always return the same js object.
952 return cached.ToNapiValue();
953 }
954
955 NapiApi::StrongRef sceneRef { ctx.This() };
956 NapiApi::Object argJS(ctx.GetEnv());
957 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
958
959 return CreateFromNativeInstance(
960 ctx.GetEnv(), obj, false /*these are owned by the scene*/, BASE_NS::countof(args), args)
961 .ToNapiValue();
962 }
963 return ctx.GetNull();
964 }
965
GetRenderMode(NapiApi::FunctionContext<> & ctx)966 napi_value SceneJS::GetRenderMode(NapiApi::FunctionContext<>& ctx)
967 {
968 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
969 if (!scene) {
970 return ctx.GetUndefined();
971 }
972 return ctx.GetNumber(uint32_t(scene->GetRenderMode().GetResult()));
973 }
974
SetRenderMode(NapiApi::FunctionContext<uint32_t> & ctx)975 void SceneJS::SetRenderMode(NapiApi::FunctionContext<uint32_t>& ctx)
976 {
977 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
978 if (!scene) {
979 return;
980 }
981 uint32_t v = ctx.Arg<0>();
982 if (v >= static_cast<uint32_t>(SCENE_NS::RenderMode::IF_DIRTY) &&
983 v <= static_cast<uint32_t>(SCENE_NS::RenderMode::MANUAL)) {
984 scene->SetRenderMode(static_cast<SCENE_NS::RenderMode>(v)).Wait();
985 }
986 }
987
CreateMeshResource(NapiApi::FunctionContext<NapiApi::Object,NapiApi::Object> & ctx)988 napi_value SceneJS::CreateMeshResource(NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object>& ctx)
989 {
990 struct Promise : public PromiseBase {
991 using PromiseBase::PromiseBase;
992 NapiApi::StrongRef this_;
993 NapiApi::StrongRef resourceParams_;
994 bool SetResult() override
995 {
996 napi_value args[] = { this_.GetValue(), resourceParams_.GetValue() };
997 auto meshResource = NapiApi::Object(GetJSConstructor(env_, "MeshResource"), BASE_NS::countof(args), args);
998 result_ = meshResource.ToNapiValue();
999 return (bool)result_;
1000 }
1001 };
1002 auto promise = new Promise(ctx.Env());
1003 auto jsPromise = promise->ToNapiValue();
1004 promise->this_ = NapiApi::StrongRef(ctx.This());
1005 NapiApi::Object resourceParams = ctx.Arg<0>();
1006 promise->resourceParams_ = NapiApi::StrongRef(resourceParams);
1007
1008 auto geometry = GeometryDefinition::GeometryDefinition::FromJs(ctx.Arg<1>());
1009 if (!geometry) {
1010 promise->Reject();
1011 return jsPromise;
1012 }
1013 napi_value geometryNapiValue;
1014 // Piggyback the native geometry definition inside the resource param. Need to ditch smart pointers for the ride.
1015 napi_create_external(ctx.Env(), geometry.release(), nullptr, nullptr, &geometryNapiValue);
1016 resourceParams.Set("GeometryDefinition", geometryNapiValue);
1017
1018 auto func = [promise]() {
1019 promise->SettleLater();
1020 return false;
1021 };
1022 auto task = META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(func));
1023 META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->AddTask(task);
1024
1025 return jsPromise;
1026 }
1027
CreateGeometry(NapiApi::FunctionContext<NapiApi::Object,NapiApi::Object> & ctx)1028 napi_value SceneJS::CreateGeometry(NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object>& ctx)
1029 {
1030 struct Promise : public PromiseBase {
1031 using PromiseBase::PromiseBase;
1032 NapiApi::StrongRef this_;
1033 NapiApi::StrongRef nodeParams_;
1034 NapiApi::StrongRef meshResource_;
1035 bool SetResult() override
1036 {
1037 napi_value args[] = { this_.GetValue(), nodeParams_.GetValue(), meshResource_.GetValue() };
1038 result_ = NapiApi::Object(GetJSConstructor(env_, "Geometry"), BASE_NS::countof(args), args).ToNapiValue();
1039 return (bool)result_;
1040 }
1041 };
1042 auto promise = new Promise(ctx.Env());
1043 auto jsPromise = promise->ToNapiValue();
1044 promise->this_ = NapiApi::StrongRef(ctx.This());
1045 promise->nodeParams_ = NapiApi::StrongRef(ctx.Arg<0>());
1046 promise->meshResource_ = NapiApi::StrongRef(ctx.Arg<1>());
1047
1048 auto func = [promise]() {
1049 promise->SettleLater();
1050 return false;
1051 };
1052 auto task = META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(func));
1053 META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->AddTask(task);
1054
1055 return jsPromise;
1056 }
1057
CreateShader(NapiApi::FunctionContext<NapiApi::Object> & ctx)1058 napi_value SceneJS::CreateShader(NapiApi::FunctionContext<NapiApi::Object>& ctx)
1059 {
1060 struct Promise : public PromiseBase {
1061 using PromiseBase::PromiseBase;
1062 NapiApi::StrongRef this_;
1063 NapiApi::StrongRef args_;
1064 BASE_NS::string uri_;
1065 BASE_NS::string name_;
1066 SCENE_NS::IShader::Ptr shader_;
1067 SCENE_NS::IScene::Ptr scene_;
1068 bool SetResult() override
1069 {
1070 napi_value args[] = {
1071 this_.GetValue(), // scene..
1072 args_.GetValue() // params.
1073 };
1074 NapiApi::Object parms(env_, args[1]);
1075
1076 napi_value null;
1077 napi_get_null(env_, &null);
1078 parms.Set("Material", null); // not bound to anything...
1079
1080 MakeNativeObjectParam(env_, shader_, BASE_NS::countof(args), args);
1081 NapiApi::Object shaderJS(GetJSConstructor(env_, "Shader"), BASE_NS::countof(args), args);
1082 result_ = shaderJS.ToNapiValue();
1083
1084 return true;
1085 };
1086 };
1087 Promise* promise = new Promise(ctx.Env());
1088 auto jsPromise = promise->ToNapiValue();
1089 promise->this_ = NapiApi::StrongRef(ctx.This());
1090 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
1091 promise->scene_ = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
1092 if (!promise->scene_) {
1093 CORE_LOG_E("promise->scene_ is null.");
1094 promise->Reject();
1095 return jsPromise;
1096 }
1097
1098 NapiApi::Object parms = ctx.Arg<0>();
1099 if (parms) {
1100 promise->name_ = parms.Get<BASE_NS::string>("name");
1101 promise->uri_ = FetchResourceOrUri(ctx.Env(), parms.Get("uri"));
1102 }
1103
1104 auto fun = [promise]() {
1105 promise->shader_ = META_NS::GetObjectRegistry().Create<SCENE_NS::IShader>(SCENE_NS::ClassId::Shader);
1106 if (!promise->shader_ || !promise->shader_->LoadShader(promise->scene_, promise->uri_).GetResult()) {
1107 LOG_W("Failed to load shader: %s", promise->uri_.c_str());
1108 }
1109 promise->SettleLater();
1110 return false;
1111 };
1112
1113 META_NS::GetTaskQueueRegistry()
1114 .GetTaskQueue(ENGINE_THREAD)
1115 ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
1116
1117 return jsPromise;
1118 }
1119
StoreBitmap(BASE_NS::string_view uri,SCENE_NS::IBitmap::Ptr bitmap)1120 void SceneJS::StoreBitmap(BASE_NS::string_view uri, SCENE_NS::IBitmap::Ptr bitmap)
1121 {
1122 CORE_NS::UniqueLock lock(mutex_);
1123 if (bitmap) {
1124 bitmaps_[uri] = bitmap;
1125 } else {
1126 // setting null. releases.
1127 bitmaps_.erase(uri);
1128 }
1129 }
FetchBitmap(BASE_NS::string_view uri)1130 SCENE_NS::IBitmap::Ptr SceneJS::FetchBitmap(BASE_NS::string_view uri)
1131 {
1132 CORE_NS::UniqueLock lock(mutex_);
1133 auto it = bitmaps_.find(uri);
1134 if (it != bitmaps_.end()) {
1135 return it->second;
1136 }
1137 return {};
1138 }
1139
CreateImage(NapiApi::FunctionContext<NapiApi::Object> & ctx)1140 napi_value SceneJS::CreateImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
1141 {
1142 using namespace RENDER_NS;
1143
1144 struct Promise : public PromiseBase {
1145 using PromiseBase::PromiseBase;
1146 NapiApi::StrongRef this_;
1147 NapiApi::StrongRef args_;
1148 BASE_NS::shared_ptr<IRenderContext> renderContext_;
1149 BASE_NS::string name_;
1150 BASE_NS::string uri_;
1151 SCENE_NS::IBitmap::Ptr bitmap_;
1152 RenderHandleReference imageHandle_;
1153 SceneJS* owner_;
1154 CORE_NS::IImageLoaderManager::LoadResult imageLoadResult_;
1155 bool cached_ { false };
1156 SCENE_NS::IScene::Ptr scene_;
1157 bool SetResult() override
1158 {
1159 if (!bitmap_) {
1160 // return the error string..
1161 NapiApi::Env e(env_);
1162 result_ = e.GetString(imageLoadResult_.error);
1163 return false;
1164 }
1165 if (cached_) {
1166 auto obj = interface_pointer_cast<META_NS::IObject>(bitmap_);
1167 result_ = FetchJsObj(obj).ToNapiValue();
1168 } else {
1169 // create the jsobject if we don't have one.
1170 napi_value args[] = {
1171 this_.GetValue(), // scene..
1172 args_.GetValue() // params.
1173 };
1174 MakeNativeObjectParam(env_, bitmap_, BASE_NS::countof(args), args);
1175 owner_->StoreBitmap(uri_, BASE_NS::move(bitmap_));
1176 NapiApi::Object imageJS(GetJSConstructor(env_, "Image"), BASE_NS::countof(args), args);
1177 result_ = imageJS.ToNapiValue();
1178 }
1179 return true;
1180 };
1181 };
1182 Promise* promise = new Promise(ctx.Env());
1183 auto jsPromise = promise->ToNapiValue();
1184 promise->owner_ = this;
1185 promise->this_ = NapiApi::StrongRef(ctx.This());
1186 promise->args_ = NapiApi::StrongRef(ctx.Arg<0>());
1187 promise->scene_ = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
1188 if (!promise->scene_) {
1189 CORE_LOG_E("promise->scene_ is null.");
1190 promise->Reject();
1191 return jsPromise;
1192 }
1193
1194 NapiApi::Object args = ctx.Arg<0>();
1195 if (args) {
1196 if (auto n = args.Get<BASE_NS::string>("name"); n.IsDefined()) {
1197 promise->name_ = n;
1198 }
1199 promise->uri_ = FetchResourceOrUri(ctx.Env(), args.Get("uri"));
1200 }
1201
1202 if (auto bitmap = FetchBitmap(promise->uri_)) {
1203 // no aliasing.. so the returned bitmaps name is.. the old one.
1204 // *fix*
1205 // oh we have it already, no need to do anything in engine side.
1206 promise->cached_ = true;
1207 promise->bitmap_ = bitmap;
1208 auto jsPromise = promise->ToNapiValue();
1209 promise->SettleNow();
1210 return jsPromise;
1211 }
1212
1213 auto& obr = META_NS::GetObjectRegistry();
1214 auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
1215 promise->renderContext_ =
1216 interface_pointer_cast<IRenderContext>(doc->GetProperty<IntfPtr>("RenderContext")->GetValue());
1217
1218 // create an IO task (to load the cpu data)
1219 auto fun = [promise]() -> META_NS::IAny::Ptr {
1220 uint32_t imageLoaderFlags = CORE_NS::IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS;
1221 auto& imageLoaderMgr = promise->renderContext_->GetEngine().GetImageLoaderManager();
1222 promise->imageLoadResult_ = imageLoaderMgr.LoadImage(promise->uri_, imageLoaderFlags);
1223 return {};
1224 };
1225
1226 // create final engine task (to create gpu resource)
1227 auto fun2 = [promise](const META_NS::IAny::Ptr&) -> META_NS::IAny::Ptr {
1228 if (!promise->imageLoadResult_.success) {
1229 LOG_E("Could not load image asset: %s", promise->imageLoadResult_.error);
1230 promise->bitmap_ = nullptr;
1231 } else {
1232 auto& gpuResourceMgr = promise->renderContext_->GetDevice().GetGpuResourceManager();
1233 RenderHandleReference imageHandle {};
1234 GpuImageDesc gpuDesc = gpuResourceMgr.CreateGpuImageDesc(promise->imageLoadResult_.image->GetImageDesc());
1235 gpuDesc.usageFlags = CORE_IMAGE_USAGE_SAMPLED_BIT | CORE_IMAGE_USAGE_TRANSFER_DST_BIT;
1236 if (gpuDesc.engineCreationFlags & EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_GENERATE_MIPS) {
1237 gpuDesc.usageFlags |= CORE_IMAGE_USAGE_TRANSFER_SRC_BIT;
1238 }
1239 gpuDesc.memoryPropertyFlags = CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
1240 promise->imageHandle_ =
1241 gpuResourceMgr.Create(promise->uri_, gpuDesc, std::move(promise->imageLoadResult_.image));
1242 promise->bitmap_ = META_NS::GetObjectRegistry().Create<SCENE_NS::IBitmap>(SCENE_NS::ClassId::Bitmap);
1243 if (auto m = interface_cast<META_NS::IMetadata>(promise->bitmap_)) {
1244 auto uri = META_NS::ConstructProperty<BASE_NS::string>("Uri", promise->uri_);
1245 m->AddProperty(uri);
1246 }
1247 if (auto i = interface_cast<SCENE_NS::IRenderResource>(promise->bitmap_)) {
1248 i->SetRenderHandle(promise->scene_->GetInternalScene(), promise->imageHandle_);
1249 }
1250 }
1251 promise->SettleLater();
1252 return {};
1253 };
1254
1255 // execute first step in io thread..
1256 // second in engine thread.
1257 // and the final in js thread (with threadsafefunction)
1258 auto ioqueue = META_NS::GetTaskQueueRegistry().GetTaskQueue(IO_QUEUE);
1259 auto enginequeue = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
1260 ioqueue->AddWaitableTask(META_NS::MakeCallback<META_NS::ITaskQueueWaitableTask>(BASE_NS::move(fun)))
1261 ->Then(META_NS::MakeCallback<META_NS::IFutureContinuation>(BASE_NS::move(fun2)), enginequeue);
1262
1263 return jsPromise;
1264 }
GetAnimations(NapiApi::FunctionContext<> & ctx)1265 napi_value SceneJS::GetAnimations(NapiApi::FunctionContext<>& ctx)
1266 {
1267 auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetThisNativeObject(ctx));
1268 if (!scene) {
1269 return ctx.GetUndefined();
1270 }
1271
1272 BASE_NS::vector<META_NS::IAnimation::Ptr> animRes;
1273 ExecSyncTask([scene, &animRes]() {
1274 animRes = scene->GetAnimations().GetResult();
1275 return META_NS::IAny::Ptr {};
1276 });
1277
1278 napi_value tmp;
1279 auto status = napi_create_array_with_length(ctx.Env(), animRes.size(), &tmp);
1280 size_t i = 0;
1281 napi_value args[] = { ctx.This().ToNapiValue(), NapiApi::Object(ctx.Env()).ToNapiValue() };
1282 for (const auto& node : animRes) {
1283 auto val = CreateFromNativeInstance(
1284 ctx.Env(), interface_pointer_cast<META_NS::IObject>(node), true, BASE_NS::countof(args), args);
1285 status = napi_set_element(ctx.Env(), tmp, i++, val.ToNapiValue());
1286 }
1287
1288 return tmp;
1289 }
1290
DisposeHook(uintptr_t token,NapiApi::Object obj)1291 void SceneJS::DisposeHook(uintptr_t token, NapiApi::Object obj)
1292 {
1293 disposables_[token] = { obj };
1294 }
ReleaseDispose(uintptr_t token)1295 void SceneJS::ReleaseDispose(uintptr_t token)
1296 {
1297 auto it = disposables_.find(token);
1298 if (it != disposables_.end()) {
1299 it->second.Reset();
1300 disposables_.erase(it->first);
1301 }
1302 }
1303
StrongDisposeHook(uintptr_t token,NapiApi::Object obj)1304 void SceneJS::StrongDisposeHook(uintptr_t token, NapiApi::Object obj)
1305 {
1306 strongDisposables_[token] = NapiApi::StrongRef(obj);
1307 }
ReleaseStrongDispose(uintptr_t token)1308 void SceneJS::ReleaseStrongDispose(uintptr_t token)
1309 {
1310 auto it = strongDisposables_.find(token);
1311 if (it != strongDisposables_.end()) {
1312 it->second.Reset();
1313 strongDisposables_.erase(it->first);
1314 }
1315 }
1316
1317 #ifdef __OHOS_PLATFORM__
1318 // This will circumvent the broken napi_set_instance_data and napi_get_instance_data implementations
1319 // on ohos platform
1320 static void* g_instanceData = nullptr;
1321
SetInstanceData(napi_env env,void * data,napi_finalize finalizeCb,void * finalizeHint)1322 napi_status SetInstanceData(napi_env env, void* data, napi_finalize finalizeCb, void* finalizeHint)
1323 {
1324 g_instanceData = data;
1325 return napi_ok;
1326 }
1327
GetInstanceData(napi_env env,void ** data)1328 napi_status GetInstanceData(napi_env env, void** data)
1329 {
1330 if (data) {
1331 *data = g_instanceData;
1332 }
1333
1334 return napi_ok;
1335 }
1336
1337 #else // __OHOS_PLATFORM__
1338
SetInstanceData(napi_env env,void * data,napi_finalize finalizeCb,void * finalizeHint)1339 napi_status SetInstanceData(napi_env env, void* data, napi_finalize finalizeCb, void* finalizeHint)
1340 {
1341 return napi_set_instance_data(env, data, finalizeCb, finalizeHint);
1342 }
1343
GetInstanceData(napi_env env,void ** data)1344 napi_status GetInstanceData(napi_env env, void** data)
1345 {
1346 return napi_get_instance_data(env, data);
1347 }
1348
1349 #endif // __OHOS_PLATFORM__
1350