• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &param);
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