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