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 "JsObjectCache.h"
19 #include "LightJS.h"
20 #include "MaterialJS.h"
21 #include "MeshResourceJS.h"
22 #include "NodeJS.h"
23 #include "ParamParsing.h"
24 #include "Promise.h"
25 #include "RenderContextJS.h"
26 #include "SamplerJS.h"
27 #include "nodejstaskqueue.h"
28 static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" };
29
30 #include <meta/api/make_callback.h>
31 #include <meta/interface/animation/intf_animation.h>
32 #include <meta/interface/intf_object_context.h>
33 #include <meta/interface/intf_task_queue.h>
34 #include <meta/interface/intf_task_queue_registry.h>
35 #include <meta/interface/property/construct_property.h>
36 #include <meta/interface/property/property_events.h>
37 #include <scene/ext/intf_render_resource.h>
38 #include <scene/ext/util.h>
39 #include <scene/interface/intf_light.h>
40 #include <scene/interface/intf_material.h>
41 #include <scene/interface/intf_mesh_resource.h>
42 #include <scene/interface/intf_node_import.h>
43 #include <scene/interface/intf_render_configuration.h>
44 #include <scene/interface/intf_render_context.h>
45 #include <scene/interface/intf_scene.h>
46 #include <scene/interface/intf_text.h>
47 #include <scene/interface/resource/intf_render_resource_manager.h>
48
49 #include <core/image/intf_image_loader_manager.h>
50 #include <core/intf_engine.h>
51 #include <render/device/intf_gpu_resource_manager.h>
52 #include <render/intf_render_context.h>
53
54 #ifdef __SCENE_ADAPTER__
55 #include "3d_widget_adapter_log.h"
56 #include "scene_adapter/scene_adapter.h"
57 #endif
58
59 // LEGACY COMPATIBILITY start
60 #include <geometry_definition/GeometryDefinition.h>
61 #include <scene/ext/intf_ecs_context.h>
62 #include <scene/ext/intf_ecs_object.h>
63 #include <scene/ext/intf_ecs_object_access.h>
64
65 #include <3d/ecs/components/name_component.h>
66 #include <3d/ecs/components/node_component.h>
67
68 #include "nodejstaskqueue.h"
69
70 // fix names to match "ye olde" implementation
71 // the bug that unnamed nodes stops hierarchy creation also still exists, works around that issue too.
Fixnames(SCENE_NS::IScene::Ptr scene)72 void Fixnames(SCENE_NS::IScene::Ptr scene)
73 {
74 struct rr {
75 uint32_t id_ = 1;
76 // not actual tree, but map of entities, and their children.
77 BASE_NS::unordered_map<CORE_NS::Entity, BASE_NS::vector<CORE_NS::Entity>> tree;
78 BASE_NS::vector<CORE_NS::Entity> roots;
79 CORE3D_NS::INodeComponentManager* cm;
80 CORE3D_NS::INameComponentManager* nm;
81 rr(SCENE_NS::IScene::Ptr scene)
82 {
83 CORE_NS::IEcs::Ptr ecs = scene->GetInternalScene()->GetEcsContext().GetNativeEcs();
84 cm = CORE_NS::GetManager<CORE3D_NS::INodeComponentManager>(*ecs);
85 nm = CORE_NS::GetManager<CORE3D_NS::INameComponentManager>(*ecs);
86 fix();
87 }
88 void scan()
89 {
90 const auto count = cm->GetComponentCount();
91 // collect nodes and their children.
92 tree.reserve(cm->GetComponentCount());
93 for (auto i = 0; i < count; i++) {
94 auto enti = cm->GetEntity(i);
95 // add node to our list. (if not yet added)
96 tree.insert({ enti, {} });
97 auto parent = cm->Get(i).parent;
98 if (CORE_NS::EntityUtil::IsValid(parent)) {
99 tree[parent].push_back(enti);
100 } else {
101 // no parent, so it's a "root"
102 roots.push_back(enti);
103 }
104 }
105 }
106 void recurse(CORE_NS::Entity id)
107 {
108 CORE3D_NS::NameComponent c = nm->Get(id);
109 if (c.name.empty()) {
110 // create a name for unnamed node.
111 c.name = "Unnamed Node ";
112 c.name += BASE_NS::to_string(id_++);
113 nm->Set(id, c);
114 }
115 for (auto c : tree[id]) {
116 recurse(c);
117 }
118 }
119 void fix()
120 {
121 scan();
122 for (auto i : roots) {
123 id_ = 1;
124 /*// force root node name to match legacy by default.
125 CORE3D_NS::NameComponent c;
126 c.name = "rootNode_";
127 nm->Set(i, c);*/
128 for (auto c : tree[i]) {
129 recurse(c);
130 }
131 }
132 }
133 } r(scene);
134 }
135 // LEGACY COMPATIBILITY end
136
137 using IntfPtr = BASE_NS::shared_ptr<CORE_NS::IInterface>;
138 using IntfWeakPtr = BASE_NS::weak_ptr<CORE_NS::IInterface>;
139
Init(napi_env env,napi_value exports)140 void SceneJS::Init(napi_env env, napi_value exports)
141 {
142 using namespace NapiApi;
143 auto loadFun = [](napi_env e, napi_callback_info cb) -> napi_value {
144 FunctionContext<> fc(e, cb);
145 return SceneJS::Load(fc);
146 };
147
148 auto getDefaultRenderContextFun = [](napi_env e, napi_callback_info cb) -> napi_value {
149 FunctionContext<> fc(e, cb);
150 return RenderContextJS::GetDefaultContext(e);
151 };
152
153 napi_property_descriptor props[] = {
154 // static methods
155 napi_property_descriptor{"load", nullptr, loadFun, nullptr, nullptr, nullptr,
156 (napi_property_attributes)(napi_static | napi_default_method)},
157 napi_property_descriptor{"getDefaultRenderContext", nullptr, getDefaultRenderContextFun, nullptr, nullptr,
158 nullptr, (napi_property_attributes)(napi_static | napi_default_method)},
159 // properties
160 GetSetProperty<uint32_t, SceneJS, &SceneJS::GetRenderMode, &SceneJS::SetRenderMode>("renderMode"),
161 GetSetProperty<NapiApi::Object, SceneJS, &SceneJS::GetEnvironment, &SceneJS::SetEnvironment>("environment"),
162 GetProperty<NapiApi::Array, SceneJS, &SceneJS::GetAnimations>("animations"),
163 // animations
164 GetProperty<BASE_NS::string, SceneJS, &SceneJS::GetRoot>("root"),
165 // scene methods
166 Method<NapiApi::FunctionContext<BASE_NS::string>, SceneJS, &SceneJS::GetNode>("getNodeByPath"),
167 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::GetResourceFactory>("getResourceFactory"),
168 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::Dispose>("destroy"),
169
170 // SceneResourceFactory methods
171 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateCamera>("createCamera"),
172 Method<NapiApi::FunctionContext<NapiApi::Object, uint32_t>, SceneJS, &SceneJS::CreateLight>("createLight"),
173 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateNode>("createNode"),
174 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateTextNode>("createTextNode"),
175 Method<NapiApi::FunctionContext<NapiApi::Object, uint32_t>, SceneJS, &SceneJS::CreateMaterial>(
176 "createMaterial"),
177 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateShader>("createShader"),
178 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateImage>("createImage"),
179 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateSampler>("createSampler"),
180 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateEnvironment>("createEnvironment"),
181 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::CreateScene>("createScene"),
182
183 Method<NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>, SceneJS,
184 &SceneJS::ImportNode>("importNode"),
185 Method<NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>, SceneJS,
186 &SceneJS::ImportScene>("importScene"),
187
188 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::RenderFrame>("renderFrame"),
189
190 Method<FunctionContext<Object, Object>, SceneJS, &SceneJS::CreateMeshResource>("createMesh"),
191 Method<FunctionContext<Object, Object>, SceneJS, &SceneJS::CreateGeometry>("createGeometry"),
192 Method<FunctionContext<Object, BASE_NS::string>, SceneJS, &SceneJS::CreateComponent>("createComponent"),
193 Method<FunctionContext<Object, BASE_NS::string>, SceneJS, &SceneJS::GetComponent>("getComponent"),
194 Method<FunctionContext<>, SceneJS, &SceneJS::GetRenderContext>("getRenderContext"),
195 };
196
197 napi_value func;
198 auto status = napi_define_class(env, "Scene", NAPI_AUTO_LENGTH, BaseObject::ctor<SceneJS>(), nullptr,
199 sizeof(props) / sizeof(props[0]), props, &func);
200
201 napi_set_named_property(env, exports, "Scene", func);
202
203 NapiApi::MyInstanceState* mis;
204 NapiApi::MyInstanceState::GetInstance(env, reinterpret_cast<void**>(&mis));
205 if (mis) {
206 mis->StoreCtor("Scene", func);
207 }
208 }
209
RegisterEnums(NapiApi::Object exports)210 void SceneJS::RegisterEnums(NapiApi::Object exports)
211 {
212 napi_value v;
213 NapiApi::Object en(exports.GetEnv());
214
215 napi_create_uint32(en.GetEnv(), static_cast<uint32_t>(SCENE_NS::RenderMode::IF_DIRTY), &v);
216 en.Set("RENDER_WHEN_DIRTY", v);
217 napi_create_uint32(en.GetEnv(), static_cast<uint32_t>(SCENE_NS::RenderMode::ALWAYS), &v);
218 en.Set("RENDER_CONTINUOUSLY", v);
219 napi_create_uint32(en.GetEnv(), static_cast<uint32_t>(SCENE_NS::RenderMode::MANUAL), &v);
220 en.Set("RENDER_MANUALLY", v);
221
222 exports.Set("RenderMode", en);
223 }
224
Load(NapiApi::FunctionContext<> & ctx)225 napi_value SceneJS::Load(NapiApi::FunctionContext<>& ctx)
226 {
227 const auto env = ctx.Env();
228 auto promise = Promise(env);
229
230 // A SceneJS instance keeps a NodeJS task queue acquired, but we're in a static method creating a SceneJS.
231 // Acquire the JS queue before invoking the JS task. Release it only after the scene is created (in the JS task).
232 const auto jsQ = GetOrCreateNodeTaskQueue(env);
233 auto queueRefCount = interface_cast<INodeJSTaskQueue>(jsQ);
234 if (queueRefCount) {
235 queueRefCount->Acquire();
236 } else {
237 return promise.Reject("Error creating a JS task queue");
238 }
239
240 BASE_NS::string uri = ExtractUri(ctx);
241 if (uri.empty()) {
242 uri = "scene://empty";
243 }
244 // make sure slashes are correct.. *eh*
245 for (;;) {
246 auto t = uri.find_first_of('\\');
247 if (t == BASE_NS::string::npos) {
248 break;
249 }
250 uri[t] = '/';
251 }
252
253 auto massageScene = [](SCENE_NS::IScene::Ptr scene) -> SCENE_NS::IScene::Ptr {
254 if (!scene || !scene->RenderConfiguration()->GetValue()) {
255 return {};
256 }
257
258 // Make sure there's a valid root node
259 scene->GetInternalScene()->GetEcsContext().CreateUnnamedRootNode();
260
261 // LEGACY COMPATIBILITY start
262 Fixnames(scene);
263 // LEGACY COMPATIBILITY end
264 auto& obr = META_NS::GetObjectRegistry();
265 AddScene(&obr, scene);
266 return scene;
267 };
268
269 auto convertToJs = [promise, queueRefCount = BASE_NS::move(queueRefCount)](SCENE_NS::IScene::Ptr scene) mutable {
270 if (!scene) {
271 promise.Reject("Scene creation failed");
272 return;
273 }
274
275 const auto env = promise.Env();
276 auto jsscene = CreateFromNativeInstance(env, scene, PtrType::STRONG, {});
277 const auto sceneJs = jsscene.GetJsWrapper<SceneJS>();
278 sceneJs->renderMan_ =
279 scene->CreateObject<SCENE_NS::IRenderResourceManager>(SCENE_NS::ClassId::RenderResourceManager).GetResult();
280
281 auto curenv = jsscene.Get<NapiApi::Object>("environment");
282 if (curenv.IsUndefinedOrNull()) {
283 // setup default env
284 NapiApi::Object argsIn(env);
285 argsIn.Set("name", "DefaultEnv");
286
287 auto res = sceneJs->CreateEnvironment(jsscene, argsIn);
288 res.Set("backgroundType", NapiApi::Value<uint32_t>(env, 1)); // image.. but with null.
289 jsscene.Set("environment", res);
290 }
291 for (auto&& c : scene->GetCameras().GetResult()) {
292 c->RenderingPipeline()->SetValue(SCENE_NS::CameraPipeline::FORWARD);
293 c->ColorTargetCustomization()->SetValue(
294 { SCENE_NS::ColorFormat { BASE_NS::BASE_FORMAT_R16G16B16A16_SFLOAT } });
295 }
296
297 const auto result = jsscene.ToNapiValue();
298
299 #ifdef __SCENE_ADAPTER__
300 // set SceneAdapter
301 auto sceneAdapter = std::make_shared<OHOS::Render3D::SceneAdapter>();
302 sceneAdapter->SetSceneObj(interface_pointer_cast<META_NS::IObject>(scene));
303 sceneJs->scene_ = sceneAdapter;
304 #endif
305 promise.Resolve(result);
306 queueRefCount->Release();
307 };
308
309 auto sceneManager = CreateSceneManager(uri);
310 if (!sceneManager) {
311 return promise.Reject("Creating scene manager failed");
312 }
313
314 auto engineQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
315 /* REMOVED DUE TO SCENE API CHANGE
316 if (uri.ends_with(".scene2")) {
317 const auto scene = SCENE_NS::LoadScene(sceneManager->GetContext(), uri);
318 META_NS::AddFutureTaskOrRunDirectly(engineQ, [=]() {
319 return massageScene(scene);
320 }).Then(BASE_NS::move(convertToJs), jsQ);
321 } else {*/
322 sceneManager->CreateScene(uri).Then(BASE_NS::move(massageScene), engineQ).Then(BASE_NS::move(convertToJs), jsQ);
323 //}
324 return promise;
325 }
326
CreateSceneManager(BASE_NS::string_view uri)327 SCENE_NS::ISceneManager::Ptr SceneJS::CreateSceneManager(BASE_NS::string_view uri)
328 {
329 auto& objRegistry = META_NS::GetObjectRegistry();
330 auto objContext = interface_pointer_cast<META_NS::IMetadata>(objRegistry.GetDefaultObjectContext());
331
332 if (uri.ends_with(".scene2")) {
333 const auto renderContext = SCENE_NS::GetBuildArg<SCENE_NS::IRenderContext::Ptr>(objContext, "RenderContext");
334 if (!renderContext || !renderContext->GetRenderer()) {
335 LOG_E("Unable to configure file resource manager for loading scene files: render context missing");
336 return {};
337 }
338 auto& fileManager = renderContext->GetRenderer()->GetEngine().GetFileManager();
339 fileManager.RegisterPath("project", ExtractPathToProject(uri), true);
340 }
341
342 return objRegistry.Create<SCENE_NS::ISceneManager>(SCENE_NS::ClassId::SceneManager, objContext);
343 }
344
ExtractPathToProject(BASE_NS::string_view uri)345 BASE_NS::string_view SceneJS::ExtractPathToProject(BASE_NS::string_view uri)
346 {
347 // Assume the scene file is in a folder that is at the root of the project.
348 // ExtractPathToProject("schema://path/to/PROJECT/assets/default.scene2") == "schema://path/to/PROJECT"
349 const auto secondToLastSlashPos = uri.find_last_of('/', uri.find_last_of('/') - 1);
350 return uri.substr(0, secondToLastSlashPos);
351 }
352
Dispose(NapiApi::FunctionContext<> & ctx)353 napi_value SceneJS::Dispose(NapiApi::FunctionContext<>& ctx)
354 {
355 DisposeNative(nullptr);
356 #ifdef __SCENE_ADAPTER__
357 if (scene_) {
358 scene_->Deinit();
359 }
360 #endif
361 return {};
362 }
DisposeNative(void *)363 void SceneJS::DisposeNative(void*)
364 {
365 if (!disposed_) {
366 disposed_ = true;
367 LOG_V("SCENE_JS::DisposeNative");
368
369 NapiApi::Object scen(env_);
370 napi_value tmp;
371 napi_create_external(
372 env_, static_cast<void*>(this),
373 [](napi_env env, void* data, void* finalize_hint) {
374 // do nothing.
375 },
376 nullptr, &tmp);
377 scen.Set("SceneJS", tmp);
378 napi_value scene = scen.ToNapiValue();
379
380 // dispose active environment
381 if (auto env = environmentJS_.GetObject()) {
382 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
383 if (func) {
384 func.Invoke(env, 1, &scene);
385 }
386 }
387 environmentJS_.Reset();
388
389 // dispose all cameras/env/etcs.
390 while (!strongDisposables_.empty()) {
391 auto it = strongDisposables_.begin();
392 auto token = it->first;
393 auto obj = it->second.GetObject();
394 if (obj) {
395 // "detaching" the nodes let's the destroy/dispose method release fully.
396 auto isNode = static_cast<NodeImpl*>(obj.GetRoot()->GetInstanceImpl(NodeImpl::ID));
397 if (isNode && isNode->IsAttached()) {
398 // it's a node, and it's attached. so detach.
399 isNode->Attached(false);
400 }
401 auto size = strongDisposables_.size();
402 NapiApi::Function func = obj.Get<NapiApi::Function>("destroy");
403 if (func) {
404 func.Invoke(obj, 1, &scene);
405 }
406
407 if (size == strongDisposables_.size()) {
408 LOG_E("Dispose function didn't dispose.");
409 strongDisposables_.erase(strongDisposables_.begin());
410 }
411 } else {
412 strongDisposables_.erase(strongDisposables_.begin());
413 }
414 }
415
416 // dispose
417 while (!disposables_.empty()) {
418 auto env = disposables_.begin()->second.GetObject();
419 if (env) {
420 auto size = disposables_.size();
421 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
422 if (func) {
423 func.Invoke(env, 1, &scene);
424 }
425 if (size == disposables_.size()) {
426 LOG_E("Weak ref dispose function didn't dispose.");
427 disposables_.erase(disposables_.begin());
428 }
429 } else {
430 disposables_.erase(disposables_.begin());
431 }
432 }
433
434 if (auto nativeScene = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject())) {
435 UnsetNativeObject();
436
437 auto r = nativeScene->RenderConfiguration()->GetValue();
438 if (r) {
439 r->Environment()->SetValue(nullptr);
440 r.reset();
441 }
442 }
443 FlushScenes();
444
445 // Release the js task queue.
446 auto tq = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
447 if (auto p = interface_cast<INodeJSTaskQueue>(tq)) {
448 p->Release();
449 // check if we can safely de-init here also.
450 if (p->IsReleased()) {
451 // destroy and unregister the queue.
452 DeinitNodeTaskQueue();
453 }
454 }
455
456 resources_.reset();
457 renderContextJS_.Reset();
458 }
459 }
GetInstanceImpl(uint32_t id)460 void* SceneJS::GetInstanceImpl(uint32_t id)
461 {
462 if (id == SceneJS::ID)
463 return this;
464 return nullptr;
465 }
Finalize(napi_env env)466 void SceneJS::Finalize(napi_env env)
467 {
468 DisposeNative(nullptr);
469 BaseObject::Finalize(env);
470 }
471
CreateNode(const NapiApi::FunctionContext<> & variableCtx,BASE_NS::string_view jsClassName,META_NS::ObjectId classId)472 napi_value SceneJS::CreateNode(
473 const NapiApi::FunctionContext<>& variableCtx, BASE_NS::string_view jsClassName, META_NS::ObjectId classId)
474 {
475 // Take only the first arg of the variable-length context.
476 auto ctx = NapiApi::FunctionContext<NapiApi::Object> { variableCtx.GetEnv(), variableCtx.GetInfo(),
477 NapiApi::ArgCount::PARTIAL };
478 const auto env = ctx.GetEnv();
479 auto promise = Promise(env);
480 auto sceneNodeParameters = ctx.Arg<0>();
481
482 auto convertToJs = [promise, jsClassName = BASE_NS::string { jsClassName },
483 sceneRef = NapiApi::StrongRef { ctx.This() },
484 paramRef = NapiApi::StrongRef { sceneNodeParameters }](SCENE_NS::INode::Ptr node) mutable {
485 const auto env = sceneRef.GetEnv();
486 napi_value args[] = { sceneRef.GetValue(), paramRef.GetValue() };
487 const auto result = CreateFromNativeInstance(env, jsClassName, node, PtrType::WEAK, args);
488 if (auto node = result.GetJsWrapper<NodeImpl>()) {
489 node->Attached(true);
490 }
491 promise.Resolve(result.ToNapiValue());
492 };
493
494 const auto nodePath = ExtractNodePath(sceneNodeParameters);
495 auto scene = ctx.This().GetNative<SCENE_NS::IScene>();
496 const auto jsQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
497 scene->CreateNode(nodePath, classId).Then(BASE_NS::move(convertToJs), jsQ);
498 return promise;
499 }
500
AddScene(META_NS::IObjectRegistry * obr,SCENE_NS::IScene::Ptr scene)501 void SceneJS::AddScene(META_NS::IObjectRegistry* obr, SCENE_NS::IScene::Ptr scene)
502 {
503 if (!obr) {
504 return;
505 }
506 auto params = interface_pointer_cast<META_NS::IMetadata>(obr->GetDefaultObjectContext());
507 if (!params) {
508 return;
509 }
510 auto duh = params->GetArrayProperty<IntfWeakPtr>("Scenes");
511 if (!duh) {
512 return;
513 }
514 duh->AddValue(interface_pointer_cast<CORE_NS::IInterface>(scene));
515 }
516
SceneJS(napi_env e,napi_callback_info i)517 SceneJS::SceneJS(napi_env e, napi_callback_info i) : BaseObject(e, i)
518 {
519 LOG_V("SceneJS ++");
520
521 // Acquire the js task queue. (make sure that as long as we have a scene, the nodejstaskqueue is useable)
522 if (auto jsQueue = interface_cast<INodeJSTaskQueue>(GetOrCreateNodeTaskQueue(e))) {
523 jsQueue->Acquire();
524 }
525
526 env_ = e; // store..
527
528 NapiApi::FunctionContext<NapiApi::Object> fromJs(e, i);
529 if (fromJs) {
530 if (auto obj = fromJs.Arg<0>().valueOrDefault()) {
531 if (obj.GetJsWrapper<RenderContextJS>()) {
532 renderContextJS_ = NapiApi::StrongRef(obj);
533 }
534 }
535 }
536
537 if (renderContextJS_.IsEmpty()) {
538 renderContextJS_ = NapiApi::StrongRef(e, RenderContextJS::GetDefaultContext(e));
539 }
540
541 if (renderContextJS_.IsEmpty()) {
542 LOG_E("No render context");
543 }
544
545 if (auto wrapper = renderContextJS_.GetObject().GetJsWrapper<RenderContextJS>()) {
546 resources_ = wrapper->GetResources();
547 }
548
549 if (!fromJs) {
550 // okay internal create. we will receive the object after.
551 return;
552 }
553 // SceneJS should be created by "Scene.Load" only.
554 // so this never happens.
555 }
556
FlushScenes()557 void SceneJS::FlushScenes()
558 {
559 ExecSyncTask([]() {
560 auto& obr = META_NS::GetObjectRegistry();
561 if (auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext())) {
562 if (auto duh = params->GetArrayProperty<IntfWeakPtr>("Scenes")) {
563 for (auto i = 0; i < duh->GetSize();) {
564 auto w = duh->GetValueAt(i);
565 if (w.lock() == nullptr) {
566 duh->RemoveAt(i);
567 } else {
568 i++;
569 }
570 }
571 }
572 }
573 return META_NS::IAny::Ptr {};
574 });
575 }
~SceneJS()576 SceneJS::~SceneJS()
577 {
578 LOG_V("SceneJS --");
579 DisposeNative(nullptr);
580 // flush all null scene objects here too.
581 FlushScenes();
582 if (!GetNativeObject()) {
583 return;
584 }
585 }
586
GetNode(NapiApi::FunctionContext<BASE_NS::string> & ctx)587 napi_value SceneJS::GetNode(NapiApi::FunctionContext<BASE_NS::string>& ctx)
588 {
589 // verify that path starts from "correct root" and then let the root node handle the rest.
590 NapiApi::Object meJs(ctx.This());
591 NapiApi::Object root = meJs.Get<NapiApi::Object>("root");
592 BASE_NS::string rootName = root.Get<BASE_NS::string>("name");
593 NapiApi::Function func = root.Get<NapiApi::Function>("getNodeByPath");
594 BASE_NS::string path = ctx.Arg<0>();
595 if (path.empty() || (path == BASE_NS::string_view("/")) || (path == rootName)) {
596 // empty or '/' or "exact rootnodename". so return root
597 return root.ToNapiValue();
598 }
599
600 // remove the "root nodes name", if given (make sure it also matches though..)
601 auto pos = 0;
602 if (path[0] != '/') {
603 pos = path.find('/', 0);
604 BASE_NS::string_view step = path.substr(0, pos);
605 if (!step.empty() && (step != rootName)) {
606 // root not matching
607 return ctx.GetNull();
608 }
609 }
610 if (pos != BASE_NS::string_view::npos) {
611 path = path.substr(pos + 1);
612 }
613
614 if (path.empty()) {
615 // after removing the root node name
616 // nothing left in path, so return root.
617 return root.ToNapiValue();
618 }
619
620 napi_value newpath = ctx.GetString(path);
621 if (newpath) {
622 return func.Invoke(root, 1, &newpath);
623 }
624 return ctx.GetNull();
625 }
GetRoot(NapiApi::FunctionContext<> & ctx)626 napi_value SceneJS::GetRoot(NapiApi::FunctionContext<>& ctx)
627 {
628 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
629 SCENE_NS::INode::Ptr root = scene->GetRootNode().GetResult();
630
631 NapiApi::StrongRef sceneRef { ctx.This() };
632 if (!sceneRef.GetObject().GetNative<SCENE_NS::IScene>()) {
633 LOG_F("INVALID SCENE!");
634 }
635
636 NapiApi::Object argJS(ctx.GetEnv());
637 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
638
639 // Store a weak ref, as these are owned by the scene.
640 auto js = CreateFromNativeInstance(ctx.GetEnv(), root, PtrType::WEAK, args);
641 if (auto node = js.GetJsWrapper<NodeImpl>()) {
642 node->Attached(true);
643 }
644 return js.ToNapiValue();
645 }
646 return ctx.GetUndefined();
647 }
648
GetEnvironment(NapiApi::FunctionContext<> & ctx)649 napi_value SceneJS::GetEnvironment(NapiApi::FunctionContext<>& ctx)
650 {
651 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
652 SCENE_NS::IEnvironment::Ptr environment;
653 auto rc = scene->RenderConfiguration()->GetValue();
654 if (rc) {
655 environment = rc->Environment()->GetValue();
656 }
657 if (environment) {
658 NapiApi::StrongRef sceneRef { ctx.This() };
659 if (!sceneRef.GetObject().GetNative<SCENE_NS::IScene>()) {
660 LOG_F("INVALID SCENE!");
661 }
662
663 NapiApi::Env env(ctx.Env());
664 NapiApi::Object argJS(env);
665 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
666
667 environmentJS_ = NapiApi::StrongRef(CreateFromNativeInstance(env, environment, PtrType::WEAK, args));
668 return environmentJS_.GetValue();
669 }
670 }
671 return ctx.GetNull();
672 }
673
SetEnvironment(NapiApi::FunctionContext<NapiApi::Object> & ctx)674 void SceneJS::SetEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
675 {
676 NapiApi::Object envObj = ctx.Arg<0>();
677 if (!envObj) {
678 return;
679 }
680 if (auto currentlySet = environmentJS_.GetObject()) {
681 if (envObj.StrictEqual(currentlySet)) { // setting the exactly the same environment. do nothing.
682 return;
683 }
684 }
685 environmentJS_ = NapiApi::StrongRef(envObj);
686
687 SCENE_NS::IEnvironment::Ptr environment = envObj.GetNative<SCENE_NS::IEnvironment>();
688
689 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
690 auto rc = scene->RenderConfiguration()->GetValue();
691 if (rc) {
692 rc->Environment()->SetValue(environment);
693 }
694 }
695 }
696
697 // resource factory
698
GetResourceFactory(NapiApi::FunctionContext<> & ctx)699 napi_value SceneJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx)
700 {
701 // just return this. as scene is the factory also.
702 return ctx.This().ToNapiValue();
703 }
CreateEnvironment(NapiApi::Object scene,NapiApi::Object argsIn)704 NapiApi::Object SceneJS::CreateEnvironment(NapiApi::Object scene, NapiApi::Object argsIn)
705 {
706 napi_env env = scene.GetEnv();
707 napi_value args[] = { scene.ToNapiValue(), argsIn.ToNapiValue() };
708 auto nativeScene = scene.GetNative<SCENE_NS::IScene>();
709 auto nativeEnv = nativeScene->CreateObject(SCENE_NS::ClassId::Environment).GetResult();
710 return CreateFromNativeInstance(env, nativeEnv, PtrType::STRONG, args);
711 }
712
CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object> & ctx)713 napi_value SceneJS::CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
714 {
715 const auto env = ctx.GetEnv();
716 napi_value args[] = { ctx.This().ToNapiValue(), ctx.Arg<0>().ToNapiValue() };
717 return Promise(env).Resolve(CreateEnvironment(ctx.This(), ctx.Arg<0>()).ToNapiValue());
718 }
719
CreateCamera(NapiApi::FunctionContext<NapiApi::Object> & ctx)720 napi_value SceneJS::CreateCamera(NapiApi::FunctionContext<NapiApi::Object>& ctx)
721 {
722 const auto env = ctx.GetEnv();
723 auto promise = Promise(env);
724
725 const auto sceneJs = ctx.This();
726 auto scene = sceneJs.GetNative<SCENE_NS::IScene>();
727
728 auto params = NapiApi::Object { ctx.Arg<0>() };
729 const auto path = ExtractNodePath(params);
730
731 // renderPipeline is (at the moment of writing) an undocumented param. Check the API docs and usage.
732 // Remove this, if it has been added to the API. Else if it's not used anywhere, remove the implementation.
733 auto pipeline = uint32_t(SCENE_NS::CameraPipeline::LIGHT_FORWARD);
734 if (const auto renderPipelineJs = params.Get("renderPipeline")) {
735 pipeline = NapiApi::Value<uint32_t>(env, renderPipelineJs);
736 }
737
738 // Don't create the camera asynchronously. There's a race condition, and we need to deactivate it immediately.
739 // Otherwise we get tons of render validation issues.
740 const auto camera = scene->CreateNode<SCENE_NS::ICamera>(path, SCENE_NS::ClassId::CameraNode).GetResult();
741 camera->SetActive(false);
742 camera->RenderingPipeline()->SetValue(SCENE_NS::CameraPipeline(pipeline));
743 napi_value args[] = { sceneJs.ToNapiValue(), params.ToNapiValue() };
744 // Store a weak ref, as these are owned by the scene.
745 const auto result = CreateFromNativeInstance(env, camera, PtrType::WEAK, args);
746 if (auto node = result.GetJsWrapper<NodeImpl>()) {
747 node->Attached(true);
748 }
749 return promise.Resolve(result.ToNapiValue());
750 }
751
CreateLight(NapiApi::FunctionContext<NapiApi::Object,uint32_t> & ctx)752 napi_value SceneJS::CreateLight(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
753 {
754 uint32_t lightType = ctx.Arg<1>();
755 BASE_NS::string ctorName;
756 if (lightType == BaseLight::DIRECTIONAL) {
757 ctorName = "DirectionalLight";
758 } else if (lightType == BaseLight::POINT) {
759 ctorName = "PointLight";
760 } else if (lightType == BaseLight::SPOT) {
761 ctorName = "SpotLight";
762 } else {
763 return Promise(ctx.GetEnv()).Reject("Unknown light type given");
764 }
765 return CreateNode(ctx, ctorName, SCENE_NS::ClassId::LightNode);
766 }
767
CreateNode(NapiApi::FunctionContext<NapiApi::Object> & ctx)768 napi_value SceneJS::CreateNode(NapiApi::FunctionContext<NapiApi::Object>& ctx)
769 {
770 return CreateNode(ctx, "Node", SCENE_NS::ClassId::Node);
771 }
772
CreateTextNode(NapiApi::FunctionContext<NapiApi::Object> & ctx)773 napi_value SceneJS::CreateTextNode(NapiApi::FunctionContext<NapiApi::Object>& ctx)
774 {
775 return CreateNode(ctx, "TextNode", SCENE_NS::ClassId::TextNode);
776 }
777
CreateMaterial(NapiApi::FunctionContext<NapiApi::Object,uint32_t> & ctx)778 napi_value SceneJS::CreateMaterial(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
779 {
780 const auto env = ctx.GetEnv();
781 auto promise = Promise(env);
782 uint32_t type = ctx.Arg<1>();
783
784 auto convertToJs = [promise, type, sceneRef = NapiApi::StrongRef(ctx.This()),
785 paramRef = NapiApi::StrongRef(ctx.Arg<0>())](SCENE_NS::IMaterial::Ptr material) mutable {
786 const auto env = promise.Env();
787 if (type == BaseMaterial::SHADER) {
788 META_NS::SetValue(material->Type(), SCENE_NS::MaterialType::CUSTOM);
789 }
790 napi_value args[] = { sceneRef.GetValue(), paramRef.GetValue() };
791 const auto result = CreateFromNativeInstance(env, material, PtrType::STRONG, args);
792 promise.Resolve(result.ToNapiValue());
793 };
794
795 const auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
796 const auto jsQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
797 scene->CreateObject<SCENE_NS::IMaterial>(SCENE_NS::ClassId::Material).Then(BASE_NS::move(convertToJs), jsQ);
798 return promise;
799 }
800
CreateScene(NapiApi::FunctionContext<> & ctx)801 napi_value SceneJS::CreateScene(NapiApi::FunctionContext<>& ctx)
802 {
803 return Load(ctx);
804 }
805
ImportNode(NapiApi::FunctionContext<BASE_NS::string,NapiApi::Object,NapiApi::Object> & ctx)806 napi_value SceneJS::ImportNode(NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>& ctx)
807 {
808 BASE_NS::string name = ctx.Arg<0>();
809 NapiApi::Object nnode = ctx.Arg<1>();
810 NapiApi::Object nparent = ctx.Arg<2>();
811 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
812 if (!nnode || !scene) {
813 return ctx.GetNull();
814 }
815
816 SCENE_NS::INode::Ptr node = nnode.GetNative<SCENE_NS::INode>();
817 SCENE_NS::INode::Ptr parent;
818 if (nparent) {
819 parent = nparent.GetNative<SCENE_NS::INode>();
820 } else {
821 parent = scene->GetRootNode().GetResult();
822 }
823
824 if (auto import = interface_cast<SCENE_NS::INodeImport>(parent)) {
825 auto importedNode = import->ImportChild(node).GetResult();
826 if (!name.empty()) {
827 if (auto named = interface_cast<META_NS::INamed>(importedNode)) {
828 named->Name()->SetValue(name);
829 }
830 }
831
832 NapiApi::StrongRef sceneRef { ctx.This() };
833 NapiApi::Object argJS(ctx.GetEnv());
834 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
835 // Store a weak ref, as these are owned by the scene.
836 auto jsNode = CreateFromNativeInstance(ctx.GetEnv(), importedNode, PtrType::WEAK, args);
837 if (auto jsNodeWrapper = jsNode.GetJsWrapper<NodeImpl>()) {
838 jsNodeWrapper->Attached(true);
839 }
840 return jsNode.ToNapiValue();
841 }
842 return ctx.GetNull();
843 }
844
ImportScene(NapiApi::FunctionContext<BASE_NS::string,NapiApi::Object,NapiApi::Object> & ctx)845 napi_value SceneJS::ImportScene(NapiApi::FunctionContext<BASE_NS::string, NapiApi::Object, NapiApi::Object>& ctx)
846 {
847 BASE_NS::string name = ctx.Arg<0>();
848 NapiApi::Object nextScene = ctx.Arg<1>();
849 NapiApi::Object nparent = ctx.Arg<2>();
850 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
851 if (!nextScene || !scene) {
852 return ctx.GetNull();
853 }
854
855 SCENE_NS::IScene::Ptr extScene = nextScene.GetNative<SCENE_NS::IScene>();
856 SCENE_NS::INode::Ptr parent;
857 if (nparent) {
858 parent = nparent.GetNative<SCENE_NS::INode>();
859 } else {
860 parent = scene->GetRootNode().GetResult();
861 }
862
863 if (auto import = interface_cast<SCENE_NS::INodeImport>(parent)) {
864 auto result = import->ImportChildScene(extScene, name).GetResult();
865
866 NapiApi::StrongRef sceneRef { ctx.This() };
867 NapiApi::Object argJS(ctx.GetEnv());
868 napi_value args[] = { sceneRef.GetObject().ToNapiValue(), argJS.ToNapiValue() };
869 // Store a weak ref, as these are owned by the scene.
870 return CreateFromNativeInstance(ctx.GetEnv(), result, PtrType::WEAK, args).ToNapiValue();
871 }
872 return ctx.GetNull();
873 }
874
GetRenderMode(NapiApi::FunctionContext<> & ctx)875 napi_value SceneJS::GetRenderMode(NapiApi::FunctionContext<>& ctx)
876 {
877 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
878 if (!scene) {
879 return ctx.GetUndefined();
880 }
881 return ctx.GetNumber(uint32_t(scene->GetRenderMode().GetResult()));
882 }
SetRenderMode(NapiApi::FunctionContext<uint32_t> & ctx)883 void SceneJS::SetRenderMode(NapiApi::FunctionContext<uint32_t>& ctx)
884 {
885 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
886 if (!scene) {
887 return;
888 }
889 uint32_t v = ctx.Arg<0>();
890 if (v >= static_cast<uint32_t>(SCENE_NS::RenderMode::IF_DIRTY) &&
891 v <= static_cast<uint32_t>(SCENE_NS::RenderMode::MANUAL)) {
892 scene->SetRenderMode(static_cast<SCENE_NS::RenderMode>(v)).Wait();
893 }
894 }
895
RenderFrame(NapiApi::FunctionContext<> & ctx)896 napi_value SceneJS::RenderFrame(NapiApi::FunctionContext<>& ctx)
897 {
898 if (ctx.ArgCount() > 1) {
899 CORE_LOG_E("render frame %d", __LINE__);
900 return ctx.GetBoolean(false);
901 }
902
903 #ifdef __SCENE_ADAPTER__
904 auto sceneAdapter = std::static_pointer_cast<OHOS::Render3D::SceneAdapter>(scene_);
905 if (sceneAdapter) {
906 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
907 if (!scene) {
908 return ctx.GetBoolean(false);
909 }
910
911 // set RenderMode based on Arg<0>.alwaysRender
912 NapiApi::FunctionContext<NapiApi::Object> paramsCtx(ctx);
913 NapiApi::Object params = paramsCtx.Arg<0>();
914 bool alwaysRender = params.IsUndefinedOrNull("alwaysRender") ? true : params.Get<bool>("alwaysRender");
915 if (currentAlwaysRender_ != alwaysRender) {
916 currentAlwaysRender_ = alwaysRender;
917 scene->SetRenderMode(static_cast<SCENE_NS::RenderMode>(alwaysRender));
918 }
919
920 sceneAdapter->SetNeedsRepaint(false);
921 sceneAdapter->RenderFrame(false);
922 } else {
923 return ctx.GetBoolean(false);
924 }
925 #else
926 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
927 if (!scene) {
928 return ctx.GetBoolean(false);
929 }
930 // todo: fix this
931 // NapiApi::Object params = ctx.Arg<0>();
932 // auto alwaysRender = params ? params.Get<bool>("alwaysRender") : true;
933 if (auto sc = scene->GetInternalScene()) {
934 sc->RenderFrame();
935 }
936 #endif
937 return ctx.GetBoolean(true);
938 }
939
CreateComponent(NapiApi::FunctionContext<NapiApi::Object,BASE_NS::string> & ctx)940 napi_value SceneJS::CreateComponent(NapiApi::FunctionContext<NapiApi::Object, BASE_NS::string>& ctx)
941 {
942 auto env = ctx.GetEnv();
943 auto promise = Promise(env);
944 if (ctx.ArgCount() > 2) { // 2: arg num
945 return promise.Reject("Invalid number of arguments");
946 }
947 NapiApi::Object nnode = ctx.Arg<0>();
948 BASE_NS::string name = ctx.Arg<1>();
949 auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject());
950 if (!scene || !nnode || name.empty()) {
951 return promise.Reject("Invalid parameters given");
952 }
953
954 auto node = nnode.GetNative<SCENE_NS::INode>();
955 if (auto comp = scene->CreateComponent(node, name).GetResult()) {
956 NapiApi::Env env(ctx.GetEnv());
957 NapiApi::Object argJS(env);
958 NapiApi::WeakRef sceneRef { ctx.This() };
959 napi_value args[] = { sceneRef.GetValue(), argJS.ToNapiValue() };
960 return promise.Resolve(
961 CreateFromNativeInstance(env, "SceneComponent", comp, PtrType::WEAK, args).ToNapiValue());
962 }
963 return promise.Reject("Could not create component");
964 }
965
GetComponent(NapiApi::FunctionContext<NapiApi::Object,BASE_NS::string> & ctx)966 napi_value SceneJS::GetComponent(NapiApi::FunctionContext<NapiApi::Object, BASE_NS::string>& ctx)
967 {
968 if (ctx.ArgCount() > 2) { // 2: arg num
969 return ctx.GetNull();
970 }
971 NapiApi::Object nnode = ctx.Arg<0>();
972 BASE_NS::string name = ctx.Arg<1>();
973 if (!nnode) {
974 return ctx.GetNull();
975 }
976 if (auto attach = nnode.GetNative<META_NS::IAttach>()) {
977 if (auto cont = attach->GetAttachmentContainer(false)) {
978 if (auto comp = cont->FindByName<SCENE_NS::IComponent>(name)) {
979 NapiApi::Env env(ctx.GetEnv());
980 NapiApi::Object argJS(env);
981 NapiApi::WeakRef sceneRef { ctx.This() };
982 napi_value args[] = { sceneRef.GetValue(), argJS.ToNapiValue() };
983 return CreateFromNativeInstance(env, "SceneComponent", comp, PtrType::WEAK, args).ToNapiValue();
984 }
985 }
986 }
987 return ctx.GetNull();
988 }
989
GetRenderContext(NapiApi::FunctionContext<> & ctx)990 napi_value SceneJS::GetRenderContext(NapiApi::FunctionContext<>& ctx)
991 {
992 return renderContextJS_.GetValue();
993 }
994
CreateMeshResource(NapiApi::FunctionContext<NapiApi::Object,NapiApi::Object> & ctx)995 napi_value SceneJS::CreateMeshResource(NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object>& ctx)
996 {
997 auto env = ctx.GetEnv();
998 auto promise = Promise(env);
999 auto geometry = GeometryDefinition::GeometryDefinition::FromJs(ctx.Arg<1>());
1000 if (!geometry) {
1001 return promise.Reject("Invalid geometry definition given");
1002 }
1003
1004 // Piggyback the native geometry definition inside the resource param. Need to ditch smart pointers for the ride.
1005 napi_value geometryNapiValue;
1006 napi_create_external(ctx.Env(), geometry.release(), nullptr, nullptr, &geometryNapiValue);
1007 NapiApi::Object resourceParams = ctx.Arg<0>();
1008 resourceParams.Set("GeometryDefinition", geometryNapiValue);
1009
1010 napi_value args[] = { ctx.This().ToNapiValue(), resourceParams.ToNapiValue() };
1011 const auto meshResource = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::MeshResource);
1012 const auto result = CreateFromNativeInstance(env, meshResource, PtrType::STRONG, args);
1013 return promise.Resolve(result.ToNapiValue());
1014 }
1015
CreateGeometry(NapiApi::FunctionContext<NapiApi::Object,NapiApi::Object> & ctx)1016 napi_value SceneJS::CreateGeometry(NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object>& ctx)
1017 {
1018 const auto env = ctx.GetEnv();
1019 auto promise = Promise(env);
1020
1021 const auto sceneJs = ctx.This();
1022 auto params = NapiApi::Object { ctx.Arg<0>() };
1023 auto meshRes = NapiApi::Object { ctx.Arg<1>() };
1024 auto tro = meshRes.GetRoot();
1025 if (!tro) {
1026 return promise.Reject("Invalid mesh resource given");
1027 }
1028 const auto meshResourceJs = static_cast<MeshResourceJS*>(tro->GetInstanceImpl(MeshResourceJS::ID));
1029 if (!meshResourceJs) {
1030 return promise.Reject("Invalid mesh resource given");
1031 }
1032
1033 // We don't use futures here, but rather just GetResult everything immediately.
1034 // Otherwise there's a race condition for a deadlock.
1035 auto scene = sceneJs.GetNative<SCENE_NS::IScene>();
1036 const auto path = ExtractNodePath(params);
1037 auto meshNode = scene->CreateNode(path, SCENE_NS::ClassId::MeshNode).GetResult();
1038 if (auto access = interface_pointer_cast<SCENE_NS::IMeshAccess>(meshNode)) {
1039 const auto mesh = meshResourceJs->CreateMesh();
1040 access->SetMesh(mesh).GetResult();
1041 napi_value args[] = { sceneJs.ToNapiValue(), params.ToNapiValue() };
1042 // Store a weak ref, as these are owned by the scene.
1043 const auto result = CreateFromNativeInstance(env, meshNode, PtrType::WEAK, args);
1044 if (auto node = result.GetJsWrapper<NodeImpl>()) {
1045 node->Attached(true);
1046 }
1047 return promise.Resolve(result.ToNapiValue());
1048 }
1049 return promise.Reject("Geometry node creation failed. Is the given node path unique and valid?");
1050 }
1051
CreateShader(NapiApi::FunctionContext<NapiApi::Object> & ctx)1052 napi_value SceneJS::CreateShader(NapiApi::FunctionContext<NapiApi::Object>& ctx)
1053 {
1054 const auto env = ctx.GetEnv();
1055 auto promise = Promise(env);
1056 NapiApi::Object resourceParams = ctx.Arg<0>();
1057 if (!resourceParams) {
1058 return promise.Reject("Invalid scene resource shader parameters given");
1059 }
1060
1061 auto uri = ExtractUri(resourceParams.Get<NapiApi::Object>("uri"));
1062 if (uri.empty()) {
1063 auto u = resourceParams.Get<BASE_NS::string>("uri");
1064 uri = ExtractUri(u);
1065 }
1066
1067 if (uri.empty()) {
1068 return promise.Reject("Invalid scene resource Shader parameters given");
1069 }
1070
1071 auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
1072 auto convertToJs = [promise, uri, sceneRef = NapiApi::StrongRef(ctx.This()),
1073 paramRef = NapiApi::StrongRef(resourceParams)](SCENE_NS::IShader::Ptr shader) mutable {
1074 if (!shader) {
1075 CORE_LOG_E("Fail to load shader but do not return %s", uri.c_str());
1076 }
1077 const auto env = promise.Env();
1078 napi_value args[] = { sceneRef.GetValue(), paramRef.GetValue() };
1079 NapiApi::Object parms(env, args[1]);
1080
1081 napi_value null;
1082 napi_get_null(env, &null);
1083 parms.Set("Material", null); // not bound to anything...
1084 const auto result = CreateFromNativeInstance(env, shader, PtrType::STRONG, args);
1085 promise.Resolve(result.ToNapiValue());
1086 };
1087
1088 auto jsQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
1089 renderMan_->LoadShader(uri).Then(BASE_NS::move(convertToJs), jsQ);
1090 return promise;
1091 }
1092
1093 // To pass LoadResult between tasks with Future::Then.
1094 META_TYPE(BASE_NS::shared_ptr<CORE_NS::IImageLoaderManager::LoadResult>);
1095
CreateImage(NapiApi::FunctionContext<NapiApi::Object> & ctx)1096 napi_value SceneJS::CreateImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
1097 {
1098 if (!renderContextJS_.IsEmpty()) {
1099 if (auto context = renderContextJS_.GetObject().GetJsWrapper<RenderContextJS>()) {
1100 NapiApi::Function f(ctx.GetEnv(), renderContextJS_.GetObject().Get("createImage"));
1101 auto param = ctx.Arg<0>().ToNapiValue();
1102 return f.Invoke(renderContextJS_.GetObject(), 1, ¶m);
1103 }
1104 }
1105
1106 return ctx.GetUndefined();
1107 }
1108
CreateSampler(NapiApi::FunctionContext<NapiApi::Object> & ctx)1109 napi_value SceneJS::CreateSampler(NapiApi::FunctionContext<NapiApi::Object>& ctx)
1110 {
1111 return Promise(ctx.GetEnv()).Resolve(SamplerJS::CreateRawJsObject(ctx.GetEnv()));
1112 }
1113
GetAnimations(NapiApi::FunctionContext<> & ctx)1114 napi_value SceneJS::GetAnimations(NapiApi::FunctionContext<>& ctx)
1115 {
1116 auto scene = ctx.This().GetNative<SCENE_NS::IScene>();
1117 if (!scene) {
1118 return ctx.GetUndefined();
1119 }
1120
1121 BASE_NS::vector<META_NS::IAnimation::Ptr> animRes;
1122 ExecSyncTask([scene, &animRes]() {
1123 animRes = scene->GetAnimations().GetResult();
1124 return META_NS::IAny::Ptr {};
1125 });
1126
1127 napi_value tmp;
1128 auto status = napi_create_array_with_length(ctx.Env(), animRes.size(), &tmp);
1129 size_t i = 0;
1130 napi_value args[] = { ctx.This().ToNapiValue(), NapiApi::Object(ctx.Env()).ToNapiValue() };
1131 for (const auto& node : animRes) {
1132 auto val = CreateFromNativeInstance(ctx.Env(), node, PtrType::STRONG, args);
1133 status = napi_set_element(ctx.Env(), tmp, i++, val.ToNapiValue());
1134 }
1135
1136 return tmp;
1137 }
1138
DisposeHook(uintptr_t token,NapiApi::Object obj)1139 void SceneJS::DisposeHook(uintptr_t token, NapiApi::Object obj)
1140 {
1141 disposables_[token] = { obj };
1142 }
ReleaseDispose(uintptr_t token)1143 void SceneJS::ReleaseDispose(uintptr_t token)
1144 {
1145 auto it = disposables_.find(token);
1146 if (it != disposables_.end()) {
1147 it->second.Reset();
1148 disposables_.erase(it->first);
1149 }
1150 }
1151
StrongDisposeHook(uintptr_t token,NapiApi::Object obj)1152 void SceneJS::StrongDisposeHook(uintptr_t token, NapiApi::Object obj)
1153 {
1154 strongDisposables_[token] = NapiApi::StrongRef(obj);
1155 }
ReleaseStrongDispose(uintptr_t token)1156 void SceneJS::ReleaseStrongDispose(uintptr_t token)
1157 {
1158 auto it = strongDisposables_.find(token);
1159 if (it != strongDisposables_.end()) {
1160 it->second.Reset();
1161 strongDisposables_.erase(it->first);
1162 }
1163 }
1164