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 #include "LightJS.h"
18 #include "MaterialJS.h"
19 static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" };
20 #include <meta/api/make_callback.h>
21 #include <meta/interface/intf_task_queue.h>
22 #include <meta/interface/intf_task_queue_registry.h>
23 #include <meta/interface/property/property_events.h>
24 #include <scene_plugin/api/camera.h> //for the classid...
25 #include <scene_plugin/api/environment_uid.h>
26 #include <scene_plugin/api/material_uid.h>
27 #include <scene_plugin/api/render_configuration_uid.h>
28 #include <scene_plugin/api/scene_uid.h>
29 #include <scene_plugin/interface/intf_ecs_scene.h>
30 #include <scene_plugin/interface/intf_material.h>
31 #include <scene_plugin/interface/intf_render_configuration.h>
32 #include <scene_plugin/interface/intf_scene.h>
33
34 #include <core/image/intf_image_loader_manager.h>
35 #include <core/intf_engine.h>
36 #include <render/device/intf_gpu_resource_manager.h>
37 #include <render/intf_render_context.h>
38
39 #ifdef __SCENE_ADAPTER__
40 #include "3d_widget_adapter_log.h"
41 #include "scene_adapter/scene_adapter.h"
42 #endif
43
44 using IntfPtr = BASE_NS::shared_ptr<CORE_NS::IInterface>;
45 using IntfWeakPtr = BASE_NS::weak_ptr<CORE_NS::IInterface>;
46
47 static META_NS::ITaskQueue::Ptr releaseThread;
48 static constexpr BASE_NS::Uid JS_RELEASE_THREAD { "3784fa96-b25b-4e9c-bbf1-e897d36f73af" };
49
Init(napi_env env,napi_value exports)50 void SceneJS::Init(napi_env env, napi_value exports)
51 {
52 using namespace NapiApi;
53 // clang-format off
54 auto loadFun = [](napi_env e,napi_callback_info cb) -> napi_value
55 {
56 FunctionContext<> fc(e,cb);
57 return SceneJS::Load(fc);
58 };
59
60 napi_property_descriptor props[] = {
61 // static methods
62 napi_property_descriptor{ "load", nullptr, loadFun, nullptr, nullptr, nullptr, (napi_property_attributes)(napi_static|napi_default_method)},
63 // properties
64 GetSetProperty<NapiApi::Object, SceneJS, &SceneJS::GetEnvironment, &SceneJS::SetEnvironment>("environment"),
65 GetProperty<NapiApi::Array,SceneJS, &SceneJS::GetAnimations>("animations"),
66 // animations
67 GetProperty<BASE_NS::string, SceneJS, &SceneJS::GetRoot>("root"),
68 // scene methods
69 Method<NapiApi::FunctionContext<BASE_NS::string>, SceneJS, &SceneJS::GetNode>("getNodeByPath"),
70 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::GetResourceFactory>("getResourceFactory"),
71 Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::Dispose>("destroy"),
72
73 // SceneResourceFactory methods
74 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateCamera>("createCamera"),
75 Method<NapiApi::FunctionContext<NapiApi::Object,uint32_t>, SceneJS, &SceneJS::CreateLight>("createLight"),
76 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateNode>("createNode"),
77 Method<NapiApi::FunctionContext<NapiApi::Object,uint32_t>, SceneJS, &SceneJS::CreateMaterial>("createMaterial"),
78 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateShader>("createShader"),
79 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateImage>("createImage"),
80 Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateEnvironment>("createEnvironment")
81 };
82 // clang-format on
83
84 napi_value func;
85 auto status = napi_define_class(env, "Scene", NAPI_AUTO_LENGTH, BaseObject::ctor<SceneJS>(), nullptr,
86 sizeof(props) / sizeof(props[0]), props, &func);
87
88 napi_set_named_property(env, exports, "Scene", func);
89
90 NapiApi::MyInstanceState* mis;
91 napi_get_instance_data(env, (void**)&mis);
92 mis->StoreCtor("Scene", func);
93 }
94 class AsyncStateBase {
95 public:
~AsyncStateBase()96 virtual ~AsyncStateBase()
97 {
98 // assert that the promise has been fulfilled.
99 CORE_ASSERT(deferred == nullptr);
100 // assert that the threadsafe func has been released.
101 CORE_ASSERT(termfun == nullptr);
102 }
103
104 // inherit from this
105 napi_deferred deferred { nullptr };
106 napi_threadsafe_function termfun { nullptr };
107 napi_value result { nullptr };
108 virtual bool finally(napi_env env) = 0;
109 template<typename A>
Flip(A & a)110 A Flip(A& a)
111 {
112 A tmp = a;
113 a = nullptr;
114 return tmp;
115 }
CallIt()116 void CallIt()
117 {
118 // should be called from engine thread only.
119 // use an extra task in engine to trigger this
120 // to woraround an issue wherer CallIt is called IN an eventhandler.
121 // as there seems to be cases where (uncommon, have no repro. but has happend)
122 // napi_release_function waits for threadsafe function completion
123 // and the "js function" is waiting for the enghen thread (which is blocked releasing the function)
124 if (auto tf = Flip(termfun)) {
125 META_NS::GetTaskQueueRegistry()
126 .GetTaskQueue(JS_RELEASE_THREAD)
127 ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move([tf]() {
128 napi_call_threadsafe_function(tf, nullptr, napi_threadsafe_function_call_mode::napi_tsfn_blocking);
129 napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
130 return false;
131 })));
132 }
133 }
134 // callable from js thread
Fail(napi_env env)135 void Fail(napi_env env)
136 {
137 napi_status status;
138 if (auto df = Flip(deferred)) {
139 status = napi_reject_deferred(env, df, result);
140 }
141 if (auto tf = Flip(termfun)) {
142 // if called from java thread. then release it here...
143 napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
144 }
145 }
Success(napi_env env)146 void Success(napi_env env)
147 {
148 napi_status status;
149 // return success
150 if (auto df = Flip(deferred)) {
151 status = napi_resolve_deferred(env, df, result);
152 }
153 if (auto tf = Flip(termfun)) {
154 // if called from java thread. then release it here...
155 napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
156 }
157 }
158 };
MakePromise(napi_env env,AsyncStateBase * data)159 napi_value MakePromise(napi_env env, AsyncStateBase* data)
160 {
161 napi_value promise;
162 napi_status status = napi_create_promise(env, &data->deferred, &promise);
163 napi_value name;
164 napi_create_string_latin1(env, "a", 1, &name);
165 status = napi_create_threadsafe_function(
166 env, nullptr, nullptr, name, 1, 1, data /*finalize_data*/,
167 [](napi_env env, void* finalize_data, void* finalize_hint) {
168 AsyncStateBase* data = (AsyncStateBase*)finalize_data;
169 delete data;
170 },
171 data /*context*/,
172 [](napi_env env, napi_value js_callback, void* context, void* inData) {
173 // IN JS THREAD. (so careful with the calls to engine)
174 napi_status status;
175 AsyncStateBase* data = (AsyncStateBase*)context;
176 status = napi_get_undefined(env, &data->result);
177 if (data->finally(env)) {
178 data->Success(env);
179 } else {
180 data->Fail(env);
181 }
182 },
183 &data->termfun);
184 return promise;
185 }
FetchResourceOrUri(napi_env e,napi_value arg)186 BASE_NS::string FetchResourceOrUri(napi_env e, napi_value arg)
187 {
188 napi_valuetype type;
189 napi_typeof(e, arg, &type);
190 if (type == napi_string) {
191 BASE_NS::string uri = NapiApi::Value<BASE_NS::string>(e, arg);
192 // set default format as system resource
193 uri.insert(0, "file://");
194 return uri;
195 }
196 if (type == napi_object) {
197 NapiApi::Object resource(e, arg);
198 uint32_t id = resource.Get<uint32_t>("id");
199 uint32_t type = resource.Get<uint32_t>("type");
200 NapiApi::Array parms = resource.Get<NapiApi::Array>("params");
201 BASE_NS::string uri;
202 if ((id == 0) && (type == 30000)) {
203 // seems like a correct rawfile.
204 uri = parms.Get<BASE_NS::string>(0);
205 }
206 if (!uri.empty()) {
207 // add the schema then
208 uri.insert(0, "OhosRawFile://");
209 }
210 return uri;
211 }
212 return "";
213 }
214
FetchResourceOrUri(NapiApi::FunctionContext<> & ctx)215 BASE_NS::string FetchResourceOrUri(NapiApi::FunctionContext<>& ctx)
216 {
217 BASE_NS::string uri;
218 NapiApi::FunctionContext<NapiApi::Object> resourceContext(ctx);
219 NapiApi::FunctionContext<BASE_NS::string> uriContext(ctx);
220
221 if (uriContext) {
222 // actually not supported anymore.
223 uri = uriContext.Arg<0>();
224 // check if there is a protocol
225 auto t = uri.find("://");
226 if (t == BASE_NS::string::npos) {
227 // no proto . so use default
228 // set default format as system resource
229 uri.insert(0, "file://");
230 }
231 } else if (resourceContext) {
232 // get it from resource then
233 NapiApi::Object resource = resourceContext.Arg<0>();
234 uint32_t id = resource.Get<uint32_t>("id");
235 uint32_t type = resource.Get<uint32_t>("type");
236 NapiApi::Array parms = resource.Get<NapiApi::Array>("params");
237 if ((id == 0) && (type == 30000)) { // 30000 : type
238 // seems like a correct rawfile.
239 uri = parms.Get<BASE_NS::string>(0);
240 }
241 if (!uri.empty()) {
242 // add the schema then
243 uri.insert(0, "OhosRawFile://");
244 }
245 }
246 return uri;
247 }
248
Load(NapiApi::FunctionContext<> & ctx)249 napi_value SceneJS::Load(NapiApi::FunctionContext<>& ctx)
250 {
251 BASE_NS::string uri = FetchResourceOrUri(ctx);
252
253 if (uri.empty()) {
254 // unsupported input..
255 return {};
256 }
257 // make sure slashes are correct.. *eh*
258 for (;;) {
259 auto t = uri.find_first_of('\\');
260 if (t == BASE_NS::string::npos) {
261 break;
262 }
263 uri[t] = '/';
264 }
265
266 auto &tr = META_NS::GetTaskQueueRegistry();
267 releaseThread = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_RELEASE_THREAD);
268 if (!releaseThread) {
269 auto &obr = META_NS::GetObjectRegistry();
270 releaseThread = obr.Create<META_NS::ITaskQueue>(META_NS::ClassId::ThreadedTaskQueue);
271 tr.RegisterTaskQueue(releaseThread, JS_RELEASE_THREAD);
272 }
273
274 struct AsyncState : public AsyncStateBase {
275 BASE_NS::string uri;
276 SCENE_NS::IScene::Ptr scene;
277 META_NS::IEvent::Token onLoadedToken { 0 };
278 bool finally(napi_env env) override
279 {
280 if (scene) {
281 auto obj = interface_pointer_cast<META_NS::IObject>(scene);
282 result = CreateJsObj(env, "Scene", obj, true, 0, nullptr);
283 // link the native object to js object.
284 StoreJsObj(obj, { env, result });
285
286 NapiApi::Object me(env, result);
287 auto curenv = me.Get<NapiApi::Object>("environment");
288 if (!curenv) {
289 // setup default env
290 NapiApi::Object argsIn(env);
291 argsIn.Set("name", "DefaultEnv");
292
293 auto* tro = (SceneJS*)(me.Native<TrueRootObject>());
294 auto res = tro->CreateEnvironment(me, argsIn);
295 res.Set("backgroundType", NapiApi::Value<uint32_t>(env, 1)); // image.. but with null.
296 me.Set("environment", res);
297 }
298
299 #ifdef __SCENE_ADAPTER__
300 // set SceneAdapter
301 auto oo = GetRootObject(env, result);
302
303 auto sceneAdapter = std::make_shared<OHOS::Render3D::SceneAdapter>();
304 sceneAdapter->SetSceneObj(oo->GetNativeObject());
305 auto sceneJs = static_cast<SceneJS*>(oo);
306 sceneJs->scene_ = sceneAdapter;
307 #endif
308 return true;
309 }
310 scene.reset();
311 return false;
312 };
313 };
314 AsyncState* data = new AsyncState();
315 data->uri = uri;
316
317 auto fun = [data]() {
318 // IN ENGINE THREAD! (so no js calls)
319 using namespace SCENE_NS;
320 auto& obr = META_NS::GetObjectRegistry();
321 auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
322 auto scene = interface_pointer_cast<SCENE_NS::IScene>(obr.Create(SCENE_NS::ClassId::Scene, params));
323 if (!scene) {
324 // insta fail
325 data->CallIt();
326 return false;
327 }
328 data->scene = scene;
329
330 auto onLoaded = META_NS::MakeCallback<META_NS::IOnChanged>([data]() {
331 bool complete = false;
332 auto scene = data->scene;
333 auto status = scene->Status()->GetValue();
334 if (status == SCENE_NS::IScene::SCENE_STATUS_READY) {
335 // still in engine thread..
336 complete = true;
337 } else if (status == SCENE_NS::IScene::SCENE_STATUS_LOADING_FAILED) {
338 data->scene.reset(); // make sure we don't have anything in result if error.
339 complete = true;
340 }
341
342 if (complete) {
343 scene->Status()->OnChanged()->RemoveHandler(data->onLoadedToken);
344 data->onLoadedToken = 0;
345 if (scene) {
346 auto& obr = META_NS::GetObjectRegistry();
347 // make sure we have renderconfig
348 auto rc = scene->RenderConfiguration()->GetValue();
349 if (!rc) {
350 // Create renderconfig
351 rc = obr.Create<SCENE_NS::IRenderConfiguration>(SCENE_NS::ClassId::RenderConfiguration);
352 scene->RenderConfiguration()->SetValue(rc);
353 }
354 /*if (auto env = rc->Environment()->GetValue(); !env) {
355 // create default env then
356 env = scene->CreateNode<SCENE_NS::IEnvironment>("default_env");
357 env->Background()->SetValue(SCENE_NS::IEnvironment::CUBEMAP);
358 rc->Environment()->SetValue(env);
359 }*/
360
361 interface_cast<IEcsScene>(scene)->RenderMode()->SetValue(IEcsScene::RenderMode::RENDER_ALWAYS);
362 auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
363 auto duh = params->GetArrayPropertyByName<IntfWeakPtr>("Scenes");
364 duh->AddValue(interface_pointer_cast<CORE_NS::IInterface>(scene));
365 }
366 // call the threadsafe func here. (let the javascript know we are done)
367 data->CallIt();
368 }
369 });
370 data->onLoadedToken = scene->Status()->OnChanged()->AddHandler(onLoaded);
371 scene->Asynchronous()->SetValue(false);
372 scene->Uri()->SetValue(data->uri);
373 return false;
374 };
375 // Should it be possible to cancel? (ie. do we need to store the token for something..)
376 META_NS::GetTaskQueueRegistry()
377 .GetTaskQueue(ENGINE_THREAD)
378 ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
379
380 return MakePromise(ctx, data);
381 }
382
Dispose(NapiApi::FunctionContext<> & ctx)383 napi_value SceneJS::Dispose(NapiApi::FunctionContext<>& ctx)
384 {
385 LOG_F("SCENE_JS::Dispose");
386 DisposeNative();
387 return {};
388 }
DisposeNative()389 void SceneJS::DisposeNative()
390 {
391 LOG_F("SCENE_JS::DisposeNative");
392 // dispose
393 while (!disposables_.empty()) {
394 auto env = disposables_.begin()->second.GetObject();
395 if (env) {
396 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
397 if (func) {
398 func.Invoke(env);
399 }
400 }
401 }
402
403 // dispose all cameras/env/etcs.
404 while (!strongDisposables_.empty()) {
405 auto it = strongDisposables_.begin();
406 auto token = it->first;
407 auto env = it->second.GetObject();
408 if (env) {
409 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
410 if (func) {
411 func.Invoke(env);
412 }
413 }
414 }
415 if (auto env = environmentJS_.GetObject()) {
416 NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
417 if (func) {
418 func.Invoke(env);
419 }
420 }
421 environmentJS_.Reset();
422 for (auto b : bitmaps_) {
423 b.second.reset();
424 }
425 if (auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject())) {
426 // reset the native object refs
427 SetNativeObject(nullptr, false);
428 SetNativeObject(nullptr, true);
429
430 ExecSyncTask([scn = BASE_NS::move(scene)]() {
431 auto r = scn->RenderConfiguration()->GetValue();
432 auto e = r->Environment()->GetValue();
433 e.reset();
434 r.reset();
435 scn->RenderConfiguration()->SetValue(nullptr);
436 return META_NS::IAny::Ptr {};
437 });
438 }
439 }
GetInstanceImpl(uint32_t id)440 void* SceneJS::GetInstanceImpl(uint32_t id)
441 {
442 if (id == SceneJS::ID) {
443 return this;
444 }
445 return nullptr;
446 }
Finalize(napi_env env)447 void SceneJS::Finalize(napi_env env)
448 {
449 // hmm.. do i need to do something BEFORE the object gets deleted..
450 BaseObject<SceneJS>::Finalize(env);
451 }
452
SceneJS(napi_env e,napi_callback_info i)453 SceneJS::SceneJS(napi_env e, napi_callback_info i) : BaseObject<SceneJS>(e, i)
454 {
455 LOG_F("SceneJS ++");
456 NapiApi::FunctionContext<NapiApi::Object> fromJs(e, i);
457
458 if (!fromJs) {
459 // okay internal create. we will receive the object after.
460 return;
461 }
462
463 // Construct native object
464 SCENE_NS::IScene::Ptr scene;
465 ExecSyncTask([&scene]() {
466 using namespace SCENE_NS;
467 auto& obr = META_NS::GetObjectRegistry();
468 auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
469 scene = interface_pointer_cast<SCENE_NS::IScene>(obr.Create(SCENE_NS::ClassId::Scene, params));
470 if (scene) {
471 // only asynch false works.. (otherwise nodes are in random state when scene is ready.. )
472 scene->Asynchronous()->SetValue(false);
473 interface_cast<IEcsScene>(scene)->RenderMode()->SetValue(IEcsScene::RenderMode::RENDER_ALWAYS);
474 auto duh = params->GetArrayPropertyByName<IntfWeakPtr>("Scenes");
475 duh->AddValue(interface_pointer_cast<CORE_NS::IInterface>(scene));
476 }
477 return META_NS::IAny::Ptr {};
478 });
479
480 // process constructor args..
481 NapiApi::Object meJs(e, fromJs.This());
482 SetNativeObject(interface_pointer_cast<META_NS::IObject>(scene), true /* KEEP STRONG REF */);
483 StoreJsObj(interface_pointer_cast<META_NS::IObject>(scene), meJs);
484
485 NapiApi::Object args = fromJs.Arg<0>();
486 if (args) {
487 if (auto name = args.Get("name")) {
488 meJs.Set("name", name);
489 }
490 if (auto uri = args.Get("uri")) {
491 meJs.Set("uri", uri);
492 }
493 }
494 }
495
~SceneJS()496 SceneJS::~SceneJS()
497 {
498 LOG_F("SceneJS --");
499 if (!GetNativeObject()) {
500 return;
501 }
502 ExecSyncTask([scene = GetNativeObject()]() {
503 auto& obr = META_NS::GetObjectRegistry();
504 auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
505 auto duh = params->GetArrayPropertyByName<IntfWeakPtr>("Scenes");
506 if (duh) {
507 for (auto i = 0; i < duh->GetSize();) {
508 auto w = duh->GetValueAt(i);
509 if (w.lock() == nullptr) {
510 duh->RemoveAt(i);
511 } else {
512 i++;
513 }
514 }
515 }
516 return META_NS::IAny::Ptr {};
517 });
518 }
519
GetNode(NapiApi::FunctionContext<BASE_NS::string> & ctx)520 napi_value SceneJS::GetNode(NapiApi::FunctionContext<BASE_NS::string>& ctx)
521 {
522 // verify that path starts from "correct root" and then let the root node handle the rest.
523 NapiApi::Object meJs(ctx, ctx.This());
524 NapiApi::Object root = meJs.Get<NapiApi::Object>("root");
525 BASE_NS::string rootName = root.Get<BASE_NS::string>("name");
526 NapiApi::Function func = root.Get<NapiApi::Function>("getNodeByPath");
527 BASE_NS::string path = ctx.Arg<0>();
528
529 // remove the "root nodes name" (make sure it also matches though..)
530 auto pos = path.find('/', 0);
531 BASE_NS::string_view step = path.substr(0, pos);
532 if (step != rootName) {
533 // root not matching
534 return ctx.GetNull();
535 }
536
537 if (pos != BASE_NS::string_view::npos) {
538 BASE_NS::string rest(path.substr(pos + 1));
539 napi_value newpath = nullptr;
540 napi_status status = napi_create_string_utf8(ctx, rest.c_str(), rest.length(), &newpath);
541 if (newpath) {
542 return func.Invoke(root, 1, &newpath);
543 }
544 return ctx.GetNull();
545 }
546 return root;
547 }
GetRoot(NapiApi::FunctionContext<> & ctx)548 napi_value SceneJS::GetRoot(NapiApi::FunctionContext<>& ctx)
549 {
550 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
551 SCENE_NS::INode::Ptr root;
552 auto cb = META_NS::MakeCallback<META_NS::ITaskQueueWaitableTask>([scene, &root]() {
553 root = scene->RootNode()->GetValue();
554 if (root) {
555 // make sure our direct descendants exist.
556 root->BuildChildren(SCENE_NS::INode::BuildBehavior::NODE_BUILD_ONLY_DIRECT_CHILDREN);
557 }
558 return META_NS::IAny::Ptr {};
559 });
560 META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->AddWaitableTask(cb)->Wait();
561 auto obj = interface_pointer_cast<META_NS::IObject>(root);
562
563 if (auto cached = FetchJsObj(obj)) {
564 // always return the same js object.
565 return cached;
566 }
567
568 NapiApi::StrongRef sceneRef { ctx, ctx.This() };
569 if (!GetNativeMeta<SCENE_NS::IScene>(sceneRef.GetObject())) {
570 CORE_LOG_F("INVALID SCENE!");
571 }
572
573 NapiApi::Object argJS(ctx);
574 napi_value args[] = { sceneRef.GetObject(), argJS };
575
576 return CreateFromNativeInstance(ctx, obj, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
577 }
578 return ctx.GetUndefined();
579 }
580
GetEnvironment(NapiApi::FunctionContext<> & ctx)581 napi_value SceneJS::GetEnvironment(NapiApi::FunctionContext<>& ctx)
582 {
583 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
584 SCENE_NS::IEnvironment::Ptr environment;
585 ExecSyncTask([scene, &environment]() {
586 auto rc = scene->RenderConfiguration()->GetValue();
587 if (rc) {
588 environment = rc->Environment()->GetValue();
589 }
590 return META_NS::IAny::Ptr {};
591 });
592 if (environment) {
593 auto obj = interface_pointer_cast<META_NS::IObject>(environment);
594 if (auto cached = FetchJsObj(obj)) {
595 // always return the same js object.
596 return cached;
597 }
598
599 NapiApi::StrongRef sceneRef { ctx, ctx.This() };
600 if (!GetNativeMeta<SCENE_NS::IScene>(sceneRef.GetObject())) {
601 CORE_LOG_F("INVALID SCENE!");
602 }
603
604 NapiApi::Object argJS(ctx);
605 napi_value args[] = { sceneRef.GetObject(), argJS };
606
607 environmentJS_ = { ctx, CreateFromNativeInstance(ctx, obj, false, BASE_NS::countof(args), args) };
608 return environmentJS_.GetValue();
609 }
610 }
611 return ctx.GetNull();
612 }
613
SetEnvironment(NapiApi::FunctionContext<NapiApi::Object> & ctx)614 void SceneJS::SetEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
615 {
616 NapiApi::Object env = ctx.Arg<0>();
617
618 if (auto currentlySet = environmentJS_.GetObject()) {
619 if ((napi_value)currentlySet == (napi_value)env) {
620 // setting the exactly the same environment. do nothing.
621 return;
622 }
623 }
624 environmentJS_.Reset();
625 if (env) {
626 environmentJS_ = { env };
627 }
628 SCENE_NS::IEnvironment::Ptr environment;
629 if (env) {
630 environment = GetNativeMeta<SCENE_NS::IEnvironment>(env);
631 }
632 if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
633 ExecSyncTask([scene, environment]() {
634 auto rc = scene->RenderConfiguration()->GetValue();
635 if (!rc) {
636 // no render config, so create it.
637 auto& obr = META_NS::GetObjectRegistry();
638 rc = obr.Create<SCENE_NS::IRenderConfiguration>(SCENE_NS::ClassId::RenderConfiguration);
639 scene->RenderConfiguration()->SetValue(rc);
640 }
641 if (rc) {
642 rc->Environment()->SetValue(environment);
643 }
644 return META_NS::IAny::Ptr {};
645 });
646 }
647 }
648
649 // resource factory
650
GetResourceFactory(NapiApi::FunctionContext<> & ctx)651 napi_value SceneJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx)
652 {
653 // just return this. as scene is the factory also.
654 return ctx.This();
655 }
CreateEnvironment(NapiApi::Object scene,NapiApi::Object argsIn)656 NapiApi::Object SceneJS::CreateEnvironment(NapiApi::Object scene, NapiApi::Object argsIn)
657 {
658 napi_env env = scene.GetEnv();
659 napi_value args[] = { scene, argsIn };
660 auto result = NapiApi::Object(GetJSConstructor(env, "Environment"), BASE_NS::countof(args), args);
661 auto ref = NapiApi::StrongRef { env, result };
662 return { env, ref.GetValue() };
663 }
CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object> & ctx)664 napi_value SceneJS::CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
665 {
666 struct AsyncState : public AsyncStateBase {
667 NapiApi::StrongRef this_;
668 NapiApi::StrongRef args_;
669 bool finally(napi_env env) override
670 {
671 auto* tro = (SceneJS*)(this_.GetObject().Native<TrueRootObject>());
672 result = tro->CreateEnvironment(this_.GetObject(), args_.GetObject());
673 return true;
674 };
675 };
676 AsyncState* data = new AsyncState();
677 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
678 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
679 napi_value result = MakePromise(ctx, data);
680 // and just instantly complete it
681 data->finally(ctx);
682 data->Success(ctx);
683 return result;
684 }
685
CreateCamera(NapiApi::FunctionContext<NapiApi::Object> & ctx)686 napi_value SceneJS::CreateCamera(NapiApi::FunctionContext<NapiApi::Object>& ctx)
687 {
688 struct AsyncState : public AsyncStateBase {
689 NapiApi::StrongRef this_;
690 NapiApi::StrongRef args_;
691 bool finally(napi_env env) override
692 {
693 napi_value args[] = {
694 this_.GetValue(), // scene..
695 args_.GetValue() // params.
696 };
697 result = NapiApi::Object(GetJSConstructor(env, "Camera"), BASE_NS::countof(args), args);
698 return true;
699 };
700 };
701 AsyncState* data = new AsyncState();
702 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
703 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
704 napi_value result = MakePromise(ctx, data);
705 // and just instantly complete it
706 data->finally(ctx);
707 data->Success(ctx);
708 return result;
709 }
710
CreateLight(NapiApi::FunctionContext<NapiApi::Object,uint32_t> & ctx)711 napi_value SceneJS::CreateLight(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
712 {
713 struct AsyncState : public AsyncStateBase {
714 NapiApi::StrongRef this_;
715 NapiApi::StrongRef args_;
716 uint32_t lightType_;
717 bool finally(napi_env env) override
718 {
719 napi_value args[] = {
720 this_.GetValue(), // scene..
721 args_.GetValue() // params.
722 };
723 NapiApi::Function func;
724 switch (lightType_) {
725 case BaseLight::DIRECTIONAL: {
726 func = GetJSConstructor(env, "DirectionalLight");
727 break;
728 }
729 case BaseLight::POINT: {
730 func = GetJSConstructor(env, "PointLight");
731 break;
732 }
733 case BaseLight::SPOT: {
734 func = GetJSConstructor(env, "SpotLight");
735 break;
736 }
737 default:
738 break;
739 }
740 if (!func) {
741 return false;
742 }
743 result = NapiApi::Object(func, BASE_NS::countof(args), args);
744 return true;
745 };
746 };
747 AsyncState* data = new AsyncState();
748 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
749 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
750 data->lightType_ = ctx.Arg<1>();
751 napi_value result = MakePromise(ctx, data);
752 // and just instantly complete it
753 data->finally(ctx);
754 data->Success(ctx);
755 return result;
756 }
757
CreateNode(NapiApi::FunctionContext<NapiApi::Object> & ctx)758 napi_value SceneJS::CreateNode(NapiApi::FunctionContext<NapiApi::Object>& ctx)
759 {
760 struct AsyncState : public AsyncStateBase {
761 NapiApi::StrongRef this_;
762 NapiApi::StrongRef args_;
763 bool finally(napi_env env) override
764 {
765 napi_value args[] = {
766 this_.GetValue(), // scene..
767 args_.GetValue() // params.
768 };
769 result = NapiApi::Object(GetJSConstructor(env, "Node"), BASE_NS::countof(args), args);
770 return true;
771 };
772 };
773 AsyncState* data = new AsyncState();
774 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
775 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
776
777 napi_value ret = MakePromise(ctx, data);
778 // and just instantly complete it
779 data->finally(ctx);
780 data->Success(ctx);
781 return ret;
782 }
783
CreateMaterial(NapiApi::FunctionContext<NapiApi::Object,uint32_t> & ctx)784 napi_value SceneJS::CreateMaterial(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
785 {
786 struct AsyncState : public AsyncStateBase {
787 NapiApi::StrongRef this_;
788 NapiApi::StrongRef args_;
789 uint32_t type_;
790 BASE_NS::string name_;
791 SCENE_NS::IMaterial::Ptr material_;
792 SCENE_NS::IScene::Ptr scene_;
793 bool finally(napi_env env) override
794 {
795 napi_value args[] = {
796 this_.GetValue(), // scene..
797 args_.GetValue() // params.
798 };
799
800 if (type_ == BaseMaterial::SHADER) {
801 MakeNativeObjectParam(env, material_, BASE_NS::countof(args), args);
802 NapiApi::Object materialJS(GetJSConstructor(env, "ShaderMaterial"), BASE_NS::countof(args), args);
803 result = materialJS;
804 } else {
805 // fail..
806 material_.reset();
807 }
808 return true;
809 };
810 };
811 AsyncState* data = new AsyncState();
812 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
813 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
814 data->type_ = ctx.Arg<1>();
815 if (ctx.Arg<0>()) {
816 NapiApi::Object parms = ctx.Arg<0>();
817 data->name_ = parms.Get<BASE_NS::string>("name");
818 }
819
820 napi_value result = MakePromise(ctx, data);
821 data->scene_ = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
822
823 // create an engine task and complete it there..
824 auto fun = [data]() {
825 data->material_ = data->scene_->CreateMaterial(data->name_);
826 data->CallIt();
827 return false;
828 };
829
830 META_NS::GetTaskQueueRegistry()
831 .GetTaskQueue(ENGINE_THREAD)
832 ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
833
834 return result;
835 }
836
CreateShader(NapiApi::FunctionContext<NapiApi::Object> & ctx)837 napi_value SceneJS::CreateShader(NapiApi::FunctionContext<NapiApi::Object>& ctx)
838 {
839 struct AsyncState : public AsyncStateBase {
840 NapiApi::StrongRef this_;
841 NapiApi::StrongRef args_;
842 BASE_NS::string uri_;
843 BASE_NS::string name_;
844 SCENE_NS::IShader::Ptr shader_;
845 bool finally(napi_env env) override
846 {
847 napi_value args[] = {
848 this_.GetValue(), // scene..
849 args_.GetValue() // params.
850 };
851 NapiApi::Object parms(env, args[1]);
852
853 napi_value null;
854 napi_get_null(env, &null);
855 parms.Set("Material", null); // not bound to anything...
856
857 MakeNativeObjectParam(env, shader_, BASE_NS::countof(args), args);
858 NapiApi::Object shaderJS(GetJSConstructor(env, "Shader"), BASE_NS::countof(args), args);
859 result = shaderJS;
860
861 return true;
862 };
863 };
864 AsyncState* data = new AsyncState();
865 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
866 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
867 if (ctx.Arg<0>()) {
868 NapiApi::Object parms = ctx.Arg<0>();
869 data->name_ = parms.Get<BASE_NS::string>("name");
870 data->uri_ = FetchResourceOrUri(ctx, parms.Get("uri"));
871 }
872
873 napi_value result = MakePromise(ctx, data);
874
875 auto fun = [data]() {
876 auto& obr = META_NS::GetObjectRegistry();
877 auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
878 auto renderContext = doc->GetPropertyByName<IntfPtr>("RenderContext")->GetValue();
879
880 auto params =
881 interface_pointer_cast<META_NS::IMetadata>(META_NS::GetObjectRegistry().Create(META_NS::ClassId::Object));
882
883 params->AddProperty(META_NS::ConstructProperty<IntfPtr>(
884 "RenderContext", renderContext, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
885
886 params->AddProperty(META_NS::ConstructProperty<BASE_NS::string>(
887 "uri", data->uri_, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
888
889 data->shader_ = META_NS::GetObjectRegistry().Create<SCENE_NS::IShader>(SCENE_NS::ClassId::Shader, params);
890 data->CallIt();
891 return false;
892 };
893
894 META_NS::GetTaskQueueRegistry()
895 .GetTaskQueue(ENGINE_THREAD)
896 ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
897
898 return result;
899 }
900
StoreBitmap(BASE_NS::string_view uri,SCENE_NS::IBitmap::Ptr bitmap)901 void SceneJS::StoreBitmap(BASE_NS::string_view uri, SCENE_NS::IBitmap::Ptr bitmap)
902 {
903 CORE_NS::UniqueLock lock(mutex_);
904 if (bitmap) {
905 bitmaps_[uri] = bitmap;
906 } else {
907 // setting null. releases.
908 bitmaps_.erase(uri);
909 }
910 }
FetchBitmap(BASE_NS::string_view uri)911 SCENE_NS::IBitmap::Ptr SceneJS::FetchBitmap(BASE_NS::string_view uri)
912 {
913 CORE_NS::UniqueLock lock(mutex_);
914 auto it = bitmaps_.find(uri);
915 if (it != bitmaps_.end()) {
916 return it->second;
917 }
918 return {};
919 }
920
CreateImage(NapiApi::FunctionContext<NapiApi::Object> & ctx)921 napi_value SceneJS::CreateImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
922 {
923 using namespace RENDER_NS;
924
925 struct AsyncState : public AsyncStateBase {
926 NapiApi::StrongRef this_;
927 NapiApi::StrongRef args_;
928 BASE_NS::shared_ptr<IRenderContext> renderContext_;
929 BASE_NS::string name_;
930 BASE_NS::string uri_;
931 SCENE_NS::IBitmap::Ptr bitmap_;
932 RenderHandleReference imageHandle_;
933 SceneJS* owner_;
934 CORE_NS::IImageLoaderManager::LoadResult imageLoadResult_;
935 bool cached_ { false };
936 bool finally(napi_env env) override
937 {
938 if (!bitmap_) {
939 // return the error string..
940 napi_create_string_utf8(env, imageLoadResult_.error, NAPI_AUTO_LENGTH, &result);
941 return false;
942 }
943 if (!cached_) {
944 // create the jsobject if we don't have one.
945 napi_value args[] = {
946 this_.GetValue(), // scene..
947 args_.GetValue() // params.
948 };
949 MakeNativeObjectParam(env, bitmap_, BASE_NS::countof(args), args);
950 owner_->StoreBitmap(uri_, BASE_NS::move(bitmap_));
951 NapiApi::Object imageJS(GetJSConstructor(env, "Image"), BASE_NS::countof(args), args);
952 result = imageJS;
953 }
954 return true;
955 };
956 };
957 AsyncState* data = new AsyncState();
958 data->owner_ = this;
959 data->this_ = NapiApi::StrongRef(ctx, ctx.This());
960 data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
961
962 NapiApi::Object args = ctx.Arg<0>();
963 if (args) {
964 if (auto n = args.Get<BASE_NS::string>("name")) {
965 data->name_ = n;
966 }
967 data->uri_ = FetchResourceOrUri(ctx, args.Get("uri"));
968 }
969
970 napi_value result = MakePromise(ctx, data);
971 if (auto bitmap = FetchBitmap(data->uri_)) {
972 // no aliasing.. so the returned bitmaps name is.. the old one.
973 // *fix*
974 // oh we have it already, no need to do anything in engine side.
975 data->cached_ = true;
976 data->bitmap_ = bitmap;
977 auto obj = interface_pointer_cast<META_NS::IObject>(data->bitmap_);
978 data->result = FetchJsObj(obj);
979 data->Success(ctx);
980 return result;
981 }
982
983 auto& obr = META_NS::GetObjectRegistry();
984 auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
985 data->renderContext_ =
986 interface_pointer_cast<IRenderContext>(doc->GetPropertyByName<IntfPtr>("RenderContext")->GetValue());
987
988 // create an IO task (to load the cpu data)
989 auto fun = [data]() -> META_NS::IAny::Ptr {
990 uint32_t imageLoaderFlags = CORE_NS::IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS;
991 auto& imageLoaderMgr = data->renderContext_->GetEngine().GetImageLoaderManager();
992 data->imageLoadResult_ = imageLoaderMgr.LoadImage(data->uri_, imageLoaderFlags);
993 return {};
994 };
995
996 // create final engine task (to create gpu resource)
997 auto fun2 = [data](const META_NS::IAny::Ptr&) -> META_NS::IAny::Ptr {
998 if (!data->imageLoadResult_.success) {
999 CORE_LOG_E("Could not load image asset: %s", data->imageLoadResult_.error);
1000 data->bitmap_ = nullptr;
1001 } else {
1002 auto& gpuResourceMgr = data->renderContext_->GetDevice().GetGpuResourceManager();
1003 RenderHandleReference imageHandle {};
1004 GpuImageDesc gpuDesc = gpuResourceMgr.CreateGpuImageDesc(data->imageLoadResult_.image->GetImageDesc());
1005 gpuDesc.usageFlags = CORE_IMAGE_USAGE_SAMPLED_BIT | CORE_IMAGE_USAGE_TRANSFER_DST_BIT;
1006 if (gpuDesc.engineCreationFlags & EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_GENERATE_MIPS) {
1007 gpuDesc.usageFlags |= CORE_IMAGE_USAGE_TRANSFER_SRC_BIT;
1008 }
1009 gpuDesc.memoryPropertyFlags = CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
1010 data->imageHandle_ = gpuResourceMgr.Create(data->uri_, gpuDesc, std::move(data->imageLoadResult_.image));
1011 data->bitmap_ = META_NS::GetObjectRegistry().Create<SCENE_NS::IBitmap>(SCENE_NS::ClassId::Bitmap);
1012 data->bitmap_->Uri()->SetValue(data->uri_);
1013 data->bitmap_->SetRenderHandle(data->imageHandle_, { gpuDesc.width, gpuDesc.height });
1014 }
1015 data->CallIt();
1016 return {};
1017 };
1018
1019 // execute first step in io thread..
1020 // second in engine thread.
1021 // and the final in js thread (with threadsafefunction)
1022 auto ioqueue = META_NS::GetTaskQueueRegistry().GetTaskQueue(IO_QUEUE);
1023 auto enginequeue = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
1024 ioqueue->AddWaitableTask(META_NS::MakeCallback<META_NS::ITaskQueueWaitableTask>(BASE_NS::move(fun)))
1025 ->Then(META_NS::MakeCallback<META_NS::IFutureContinuation>(BASE_NS::move(fun2)), enginequeue);
1026
1027 return result;
1028 }
GetAnimations(NapiApi::FunctionContext<> & ctx)1029 napi_value SceneJS::GetAnimations(NapiApi::FunctionContext<>& ctx)
1030 {
1031 auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetThisNativeObject(ctx));
1032 if (!scene) {
1033 return ctx.GetUndefined();
1034 }
1035
1036 BASE_NS::vector<META_NS::IAnimation::Ptr> animRes;
1037 ExecSyncTask([scene, &animRes]() {
1038 animRes = scene->GetAnimations();
1039 return META_NS::IAny::Ptr {};
1040 });
1041
1042 napi_value tmp;
1043 auto status = napi_create_array_with_length(ctx, animRes.size(), &tmp);
1044 size_t i = 0;
1045 napi_value args[] = { ctx.This() };
1046 for (const auto& node : animRes) {
1047 napi_value val;
1048 val = CreateFromNativeInstance(
1049 ctx, interface_pointer_cast<META_NS::IObject>(node), false, BASE_NS::countof(args), args);
1050 status = napi_set_element(ctx, tmp, i++, val);
1051
1052 // disposables_[
1053 }
1054
1055 return tmp;
1056 }
1057
DisposeHook(uintptr_t token,NapiApi::Object obj)1058 void SceneJS::DisposeHook(uintptr_t token, NapiApi::Object obj)
1059 {
1060 disposables_[token] = { obj };
1061 }
ReleaseDispose(uintptr_t token)1062 void SceneJS::ReleaseDispose(uintptr_t token)
1063 {
1064 auto it = disposables_.find(token);
1065 if (it != disposables_.end()) {
1066 it->second.Reset();
1067 disposables_.erase(it->first);
1068 }
1069 }
1070
StrongDisposeHook(uintptr_t token,NapiApi::Object obj)1071 void SceneJS::StrongDisposeHook(uintptr_t token, NapiApi::Object obj)
1072 {
1073 strongDisposables_[token] = { obj };
1074 }
ReleaseStrongDispose(uintptr_t token)1075 void SceneJS::ReleaseStrongDispose(uintptr_t token)
1076 {
1077 auto it = strongDisposables_.find(token);
1078 if (it != strongDisposables_.end()) {
1079 it->second.Reset();
1080 strongDisposables_.erase(it->first);
1081 }
1082 }
1083