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