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