/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SceneJS.h"
#include "LightJS.h"
#include "MaterialJS.h"
static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" };
#include
#include
#include
#include
#include //for the classid...
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __SCENE_ADAPTER__
#include "3d_widget_adapter_log.h"
#include "scene_adapter/scene_adapter.h"
#endif
using IntfPtr = BASE_NS::shared_ptr;
using IntfWeakPtr = BASE_NS::weak_ptr;
static META_NS::ITaskQueue::Ptr releaseThread;
static constexpr BASE_NS::Uid JS_RELEASE_THREAD { "3784fa96-b25b-4e9c-bbf1-e897d36f73af" };
void SceneJS::Init(napi_env env, napi_value exports)
{
using namespace NapiApi;
// clang-format off
auto loadFun = [](napi_env e,napi_callback_info cb) -> napi_value
{
FunctionContext<> fc(e,cb);
return SceneJS::Load(fc);
};
napi_property_descriptor props[] = {
// static methods
napi_property_descriptor{ "load", nullptr, loadFun, nullptr, nullptr, nullptr, (napi_property_attributes)(napi_static|napi_default_method)},
// properties
GetSetProperty("environment"),
GetProperty("animations"),
// animations
GetProperty("root"),
// scene methods
Method, SceneJS, &SceneJS::GetNode>("getNodeByPath"),
Method, SceneJS, &SceneJS::GetResourceFactory>("getResourceFactory"),
Method, SceneJS, &SceneJS::Dispose>("destroy"),
// SceneResourceFactory methods
Method, SceneJS, &SceneJS::CreateCamera>("createCamera"),
Method, SceneJS, &SceneJS::CreateLight>("createLight"),
Method, SceneJS, &SceneJS::CreateNode>("createNode"),
Method, SceneJS, &SceneJS::CreateMaterial>("createMaterial"),
Method, SceneJS, &SceneJS::CreateShader>("createShader"),
Method, SceneJS, &SceneJS::CreateImage>("createImage"),
Method, SceneJS, &SceneJS::CreateEnvironment>("createEnvironment")
};
// clang-format on
napi_value func;
auto status = napi_define_class(env, "Scene", NAPI_AUTO_LENGTH, BaseObject::ctor(), nullptr,
sizeof(props) / sizeof(props[0]), props, &func);
napi_set_named_property(env, exports, "Scene", func);
NapiApi::MyInstanceState* mis;
napi_get_instance_data(env, (void**)&mis);
mis->StoreCtor("Scene", func);
}
class AsyncStateBase {
public:
virtual ~AsyncStateBase()
{
// assert that the promise has been fulfilled.
CORE_ASSERT(deferred == nullptr);
// assert that the threadsafe func has been released.
CORE_ASSERT(termfun == nullptr);
}
// inherit from this
napi_deferred deferred { nullptr };
napi_threadsafe_function termfun { nullptr };
napi_value result { nullptr };
virtual bool finally(napi_env env) = 0;
template
A Flip(A& a)
{
A tmp = a;
a = nullptr;
return tmp;
}
void CallIt()
{
// should be called from engine thread only.
// use an extra task in engine to trigger this
// to woraround an issue wherer CallIt is called IN an eventhandler.
// as there seems to be cases where (uncommon, have no repro. but has happend)
// napi_release_function waits for threadsafe function completion
// and the "js function" is waiting for the enghen thread (which is blocked releasing the function)
if (auto tf = Flip(termfun)) {
META_NS::GetTaskQueueRegistry()
.GetTaskQueue(JS_RELEASE_THREAD)
->AddTask(META_NS::MakeCallback(BASE_NS::move([tf]() {
napi_call_threadsafe_function(tf, nullptr, napi_threadsafe_function_call_mode::napi_tsfn_blocking);
napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
return false;
})));
}
}
// callable from js thread
void Fail(napi_env env)
{
napi_status status;
if (auto df = Flip(deferred)) {
status = napi_reject_deferred(env, df, result);
}
if (auto tf = Flip(termfun)) {
// if called from java thread. then release it here...
napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
}
}
void Success(napi_env env)
{
napi_status status;
// return success
if (auto df = Flip(deferred)) {
status = napi_resolve_deferred(env, df, result);
}
if (auto tf = Flip(termfun)) {
// if called from java thread. then release it here...
napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
}
}
};
napi_value MakePromise(napi_env env, AsyncStateBase* data)
{
napi_value promise;
napi_status status = napi_create_promise(env, &data->deferred, &promise);
napi_value name;
napi_create_string_latin1(env, "a", 1, &name);
status = napi_create_threadsafe_function(
env, nullptr, nullptr, name, 1, 1, data /*finalize_data*/,
[](napi_env env, void* finalize_data, void* finalize_hint) {
AsyncStateBase* data = (AsyncStateBase*)finalize_data;
delete data;
},
data /*context*/,
[](napi_env env, napi_value js_callback, void* context, void* inData) {
// IN JS THREAD. (so careful with the calls to engine)
napi_status status;
AsyncStateBase* data = (AsyncStateBase*)context;
status = napi_get_undefined(env, &data->result);
if (data->finally(env)) {
data->Success(env);
} else {
data->Fail(env);
}
},
&data->termfun);
return promise;
}
BASE_NS::string FetchResourceOrUri(napi_env e, napi_value arg)
{
napi_valuetype type;
napi_typeof(e, arg, &type);
if (type == napi_string) {
BASE_NS::string uri = NapiApi::Value(e, arg);
// set default format as system resource
uri.insert(0, "file://");
return uri;
}
if (type == napi_object) {
NapiApi::Object resource(e, arg);
uint32_t id = resource.Get("id");
uint32_t type = resource.Get("type");
NapiApi::Array parms = resource.Get("params");
BASE_NS::string uri;
if ((id == 0) && (type == 30000)) {
// seems like a correct rawfile.
uri = parms.Get(0);
}
if (!uri.empty()) {
// add the schema then
uri.insert(0, "OhosRawFile://");
}
return uri;
}
return "";
}
BASE_NS::string FetchResourceOrUri(NapiApi::FunctionContext<>& ctx)
{
BASE_NS::string uri;
NapiApi::FunctionContext resourceContext(ctx);
NapiApi::FunctionContext uriContext(ctx);
if (uriContext) {
// actually not supported anymore.
uri = uriContext.Arg<0>();
// check if there is a protocol
auto t = uri.find("://");
if (t == BASE_NS::string::npos) {
// no proto . so use default
// set default format as system resource
uri.insert(0, "file://");
}
} else if (resourceContext) {
// get it from resource then
NapiApi::Object resource = resourceContext.Arg<0>();
uint32_t id = resource.Get("id");
uint32_t type = resource.Get("type");
NapiApi::Array parms = resource.Get("params");
if ((id == 0) && (type == 30000)) { // 30000 : type
// seems like a correct rawfile.
uri = parms.Get(0);
}
if (!uri.empty()) {
// add the schema then
uri.insert(0, "OhosRawFile://");
}
}
return uri;
}
napi_value SceneJS::Load(NapiApi::FunctionContext<>& ctx)
{
BASE_NS::string uri = FetchResourceOrUri(ctx);
if (uri.empty()) {
// unsupported input..
return {};
}
// make sure slashes are correct.. *eh*
for (;;) {
auto t = uri.find_first_of('\\');
if (t == BASE_NS::string::npos) {
break;
}
uri[t] = '/';
}
auto &tr = META_NS::GetTaskQueueRegistry();
releaseThread = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_RELEASE_THREAD);
if (!releaseThread) {
auto &obr = META_NS::GetObjectRegistry();
releaseThread = obr.Create(META_NS::ClassId::ThreadedTaskQueue);
tr.RegisterTaskQueue(releaseThread, JS_RELEASE_THREAD);
}
struct AsyncState : public AsyncStateBase {
BASE_NS::string uri;
SCENE_NS::IScene::Ptr scene;
META_NS::IEvent::Token onLoadedToken { 0 };
bool finally(napi_env env) override
{
if (scene) {
auto obj = interface_pointer_cast(scene);
result = CreateJsObj(env, "Scene", obj, true, 0, nullptr);
// link the native object to js object.
StoreJsObj(obj, { env, result });
NapiApi::Object me(env, result);
auto curenv = me.Get("environment");
if (!curenv) {
// setup default env
NapiApi::Object argsIn(env);
argsIn.Set("name", "DefaultEnv");
auto* tro = (SceneJS*)(me.Native());
auto res = tro->CreateEnvironment(me, argsIn);
res.Set("backgroundType", NapiApi::Value(env, 1)); // image.. but with null.
me.Set("environment", res);
}
#ifdef __SCENE_ADAPTER__
// set SceneAdapter
auto oo = GetRootObject(env, result);
auto sceneAdapter = std::make_shared();
sceneAdapter->SetSceneObj(oo->GetNativeObject());
auto sceneJs = static_cast(oo);
sceneJs->scene_ = sceneAdapter;
#endif
return true;
}
scene.reset();
return false;
};
};
AsyncState* data = new AsyncState();
data->uri = uri;
auto fun = [data]() {
// IN ENGINE THREAD! (so no js calls)
using namespace SCENE_NS;
auto& obr = META_NS::GetObjectRegistry();
auto params = interface_pointer_cast(obr.GetDefaultObjectContext());
auto scene = interface_pointer_cast(obr.Create(SCENE_NS::ClassId::Scene, params));
if (!scene) {
// insta fail
data->CallIt();
return false;
}
data->scene = scene;
auto onLoaded = META_NS::MakeCallback([data]() {
bool complete = false;
auto scene = data->scene;
auto status = scene->Status()->GetValue();
if (status == SCENE_NS::IScene::SCENE_STATUS_READY) {
// still in engine thread..
complete = true;
} else if (status == SCENE_NS::IScene::SCENE_STATUS_LOADING_FAILED) {
data->scene.reset(); // make sure we don't have anything in result if error.
complete = true;
}
if (complete) {
scene->Status()->OnChanged()->RemoveHandler(data->onLoadedToken);
data->onLoadedToken = 0;
if (scene) {
auto& obr = META_NS::GetObjectRegistry();
// make sure we have renderconfig
auto rc = scene->RenderConfiguration()->GetValue();
if (!rc) {
// Create renderconfig
rc = obr.Create(SCENE_NS::ClassId::RenderConfiguration);
scene->RenderConfiguration()->SetValue(rc);
}
/*if (auto env = rc->Environment()->GetValue(); !env) {
// create default env then
env = scene->CreateNode("default_env");
env->Background()->SetValue(SCENE_NS::IEnvironment::CUBEMAP);
rc->Environment()->SetValue(env);
}*/
interface_cast(scene)->RenderMode()->SetValue(IEcsScene::RenderMode::RENDER_ALWAYS);
auto params = interface_pointer_cast(obr.GetDefaultObjectContext());
auto duh = params->GetArrayPropertyByName("Scenes");
duh->AddValue(interface_pointer_cast(scene));
}
// call the threadsafe func here. (let the javascript know we are done)
data->CallIt();
}
});
data->onLoadedToken = scene->Status()->OnChanged()->AddHandler(onLoaded);
scene->Asynchronous()->SetValue(false);
scene->Uri()->SetValue(data->uri);
return false;
};
// Should it be possible to cancel? (ie. do we need to store the token for something..)
META_NS::GetTaskQueueRegistry()
.GetTaskQueue(ENGINE_THREAD)
->AddTask(META_NS::MakeCallback(BASE_NS::move(fun)));
return MakePromise(ctx, data);
}
napi_value SceneJS::Dispose(NapiApi::FunctionContext<>& ctx)
{
LOG_F("SCENE_JS::Dispose");
DisposeNative();
return {};
}
void SceneJS::DisposeNative()
{
LOG_F("SCENE_JS::DisposeNative");
// dispose
while (!disposables_.empty()) {
auto env = disposables_.begin()->second.GetObject();
if (env) {
NapiApi::Function func = env.Get("destroy");
if (func) {
func.Invoke(env);
}
}
}
// dispose all cameras/env/etcs.
while (!strongDisposables_.empty()) {
auto it = strongDisposables_.begin();
auto token = it->first;
auto env = it->second.GetObject();
if (env) {
NapiApi::Function func = env.Get("destroy");
if (func) {
func.Invoke(env);
}
}
}
if (auto env = environmentJS_.GetObject()) {
NapiApi::Function func = env.Get("destroy");
if (func) {
func.Invoke(env);
}
}
environmentJS_.Reset();
for (auto b : bitmaps_) {
b.second.reset();
}
if (auto scene = interface_pointer_cast(GetNativeObject())) {
// reset the native object refs
SetNativeObject(nullptr, false);
SetNativeObject(nullptr, true);
ExecSyncTask([scn = BASE_NS::move(scene)]() {
auto r = scn->RenderConfiguration()->GetValue();
auto e = r->Environment()->GetValue();
e.reset();
r.reset();
scn->RenderConfiguration()->SetValue(nullptr);
return META_NS::IAny::Ptr {};
});
}
}
void* SceneJS::GetInstanceImpl(uint32_t id)
{
if (id == SceneJS::ID) {
return this;
}
return nullptr;
}
void SceneJS::Finalize(napi_env env)
{
// hmm.. do i need to do something BEFORE the object gets deleted..
BaseObject::Finalize(env);
}
SceneJS::SceneJS(napi_env e, napi_callback_info i) : BaseObject(e, i)
{
LOG_F("SceneJS ++");
NapiApi::FunctionContext fromJs(e, i);
if (!fromJs) {
// okay internal create. we will receive the object after.
return;
}
// Construct native object
SCENE_NS::IScene::Ptr scene;
ExecSyncTask([&scene]() {
using namespace SCENE_NS;
auto& obr = META_NS::GetObjectRegistry();
auto params = interface_pointer_cast(obr.GetDefaultObjectContext());
scene = interface_pointer_cast(obr.Create(SCENE_NS::ClassId::Scene, params));
if (scene) {
// only asynch false works.. (otherwise nodes are in random state when scene is ready.. )
scene->Asynchronous()->SetValue(false);
interface_cast(scene)->RenderMode()->SetValue(IEcsScene::RenderMode::RENDER_ALWAYS);
auto duh = params->GetArrayPropertyByName("Scenes");
duh->AddValue(interface_pointer_cast(scene));
}
return META_NS::IAny::Ptr {};
});
// process constructor args..
NapiApi::Object meJs(e, fromJs.This());
SetNativeObject(interface_pointer_cast(scene), true /* KEEP STRONG REF */);
StoreJsObj(interface_pointer_cast(scene), meJs);
NapiApi::Object args = fromJs.Arg<0>();
if (args) {
if (auto name = args.Get("name")) {
meJs.Set("name", name);
}
if (auto uri = args.Get("uri")) {
meJs.Set("uri", uri);
}
}
}
SceneJS::~SceneJS()
{
LOG_F("SceneJS --");
if (!GetNativeObject()) {
return;
}
ExecSyncTask([scene = GetNativeObject()]() {
auto& obr = META_NS::GetObjectRegistry();
auto params = interface_pointer_cast(obr.GetDefaultObjectContext());
auto duh = params->GetArrayPropertyByName("Scenes");
if (duh) {
for (auto i = 0; i < duh->GetSize();) {
auto w = duh->GetValueAt(i);
if (w.lock() == nullptr) {
duh->RemoveAt(i);
} else {
i++;
}
}
}
return META_NS::IAny::Ptr {};
});
}
napi_value SceneJS::GetNode(NapiApi::FunctionContext& ctx)
{
// verify that path starts from "correct root" and then let the root node handle the rest.
NapiApi::Object meJs(ctx, ctx.This());
NapiApi::Object root = meJs.Get("root");
BASE_NS::string rootName = root.Get("name");
NapiApi::Function func = root.Get("getNodeByPath");
BASE_NS::string path = ctx.Arg<0>();
// remove the "root nodes name" (make sure it also matches though..)
auto pos = path.find('/', 0);
BASE_NS::string_view step = path.substr(0, pos);
if (step != rootName) {
// root not matching
return ctx.GetNull();
}
if (pos != BASE_NS::string_view::npos) {
BASE_NS::string rest(path.substr(pos + 1));
napi_value newpath = nullptr;
napi_status status = napi_create_string_utf8(ctx, rest.c_str(), rest.length(), &newpath);
if (newpath) {
return func.Invoke(root, 1, &newpath);
}
return ctx.GetNull();
}
return root;
}
napi_value SceneJS::GetRoot(NapiApi::FunctionContext<>& ctx)
{
if (auto scene = interface_cast(GetNativeObject())) {
SCENE_NS::INode::Ptr root;
auto cb = META_NS::MakeCallback([scene, &root]() {
root = scene->RootNode()->GetValue();
if (root) {
// make sure our direct descendants exist.
root->BuildChildren(SCENE_NS::INode::BuildBehavior::NODE_BUILD_ONLY_DIRECT_CHILDREN);
}
return META_NS::IAny::Ptr {};
});
META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->AddWaitableTask(cb)->Wait();
auto obj = interface_pointer_cast(root);
if (auto cached = FetchJsObj(obj)) {
// always return the same js object.
return cached;
}
NapiApi::StrongRef sceneRef { ctx, ctx.This() };
if (!GetNativeMeta(sceneRef.GetObject())) {
CORE_LOG_F("INVALID SCENE!");
}
NapiApi::Object argJS(ctx);
napi_value args[] = { sceneRef.GetObject(), argJS };
return CreateFromNativeInstance(ctx, obj, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
}
return ctx.GetUndefined();
}
napi_value SceneJS::GetEnvironment(NapiApi::FunctionContext<>& ctx)
{
if (auto scene = interface_cast(GetNativeObject())) {
SCENE_NS::IEnvironment::Ptr environment;
ExecSyncTask([scene, &environment]() {
auto rc = scene->RenderConfiguration()->GetValue();
if (rc) {
environment = rc->Environment()->GetValue();
}
return META_NS::IAny::Ptr {};
});
if (environment) {
auto obj = interface_pointer_cast(environment);
if (auto cached = FetchJsObj(obj)) {
// always return the same js object.
return cached;
}
NapiApi::StrongRef sceneRef { ctx, ctx.This() };
if (!GetNativeMeta(sceneRef.GetObject())) {
CORE_LOG_F("INVALID SCENE!");
}
NapiApi::Object argJS(ctx);
napi_value args[] = { sceneRef.GetObject(), argJS };
environmentJS_ = { ctx, CreateFromNativeInstance(ctx, obj, false, BASE_NS::countof(args), args) };
return environmentJS_.GetValue();
}
}
return ctx.GetNull();
}
void SceneJS::SetEnvironment(NapiApi::FunctionContext& ctx)
{
NapiApi::Object env = ctx.Arg<0>();
if (auto currentlySet = environmentJS_.GetObject()) {
if ((napi_value)currentlySet == (napi_value)env) {
// setting the exactly the same environment. do nothing.
return;
}
}
environmentJS_.Reset();
if (env) {
environmentJS_ = { env };
}
SCENE_NS::IEnvironment::Ptr environment;
if (env) {
environment = GetNativeMeta(env);
}
if (auto scene = interface_cast(GetNativeObject())) {
ExecSyncTask([scene, environment]() {
auto rc = scene->RenderConfiguration()->GetValue();
if (!rc) {
// no render config, so create it.
auto& obr = META_NS::GetObjectRegistry();
rc = obr.Create(SCENE_NS::ClassId::RenderConfiguration);
scene->RenderConfiguration()->SetValue(rc);
}
if (rc) {
rc->Environment()->SetValue(environment);
}
return META_NS::IAny::Ptr {};
});
}
}
// resource factory
napi_value SceneJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx)
{
// just return this. as scene is the factory also.
return ctx.This();
}
NapiApi::Object SceneJS::CreateEnvironment(NapiApi::Object scene, NapiApi::Object argsIn)
{
napi_env env = scene.GetEnv();
napi_value args[] = { scene, argsIn };
auto result = NapiApi::Object(GetJSConstructor(env, "Environment"), BASE_NS::countof(args), args);
auto ref = NapiApi::StrongRef { env, result };
return { env, ref.GetValue() };
}
napi_value SceneJS::CreateEnvironment(NapiApi::FunctionContext& ctx)
{
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
bool finally(napi_env env) override
{
auto* tro = (SceneJS*)(this_.GetObject().Native());
result = tro->CreateEnvironment(this_.GetObject(), args_.GetObject());
return true;
};
};
AsyncState* data = new AsyncState();
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
napi_value result = MakePromise(ctx, data);
// and just instantly complete it
data->finally(ctx);
data->Success(ctx);
return result;
}
napi_value SceneJS::CreateCamera(NapiApi::FunctionContext& ctx)
{
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
bool finally(napi_env env) override
{
napi_value args[] = {
this_.GetValue(), // scene..
args_.GetValue() // params.
};
result = NapiApi::Object(GetJSConstructor(env, "Camera"), BASE_NS::countof(args), args);
return true;
};
};
AsyncState* data = new AsyncState();
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
napi_value result = MakePromise(ctx, data);
// and just instantly complete it
data->finally(ctx);
data->Success(ctx);
return result;
}
napi_value SceneJS::CreateLight(NapiApi::FunctionContext& ctx)
{
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
uint32_t lightType_;
bool finally(napi_env env) override
{
napi_value args[] = {
this_.GetValue(), // scene..
args_.GetValue() // params.
};
NapiApi::Function func;
switch (lightType_) {
case BaseLight::DIRECTIONAL: {
func = GetJSConstructor(env, "DirectionalLight");
break;
}
case BaseLight::POINT: {
func = GetJSConstructor(env, "PointLight");
break;
}
case BaseLight::SPOT: {
func = GetJSConstructor(env, "SpotLight");
break;
}
default:
break;
}
if (!func) {
return false;
}
result = NapiApi::Object(func, BASE_NS::countof(args), args);
return true;
};
};
AsyncState* data = new AsyncState();
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
data->lightType_ = ctx.Arg<1>();
napi_value result = MakePromise(ctx, data);
// and just instantly complete it
data->finally(ctx);
data->Success(ctx);
return result;
}
napi_value SceneJS::CreateNode(NapiApi::FunctionContext& ctx)
{
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
bool finally(napi_env env) override
{
napi_value args[] = {
this_.GetValue(), // scene..
args_.GetValue() // params.
};
result = NapiApi::Object(GetJSConstructor(env, "Node"), BASE_NS::countof(args), args);
return true;
};
};
AsyncState* data = new AsyncState();
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
napi_value ret = MakePromise(ctx, data);
// and just instantly complete it
data->finally(ctx);
data->Success(ctx);
return ret;
}
napi_value SceneJS::CreateMaterial(NapiApi::FunctionContext& ctx)
{
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
uint32_t type_;
BASE_NS::string name_;
SCENE_NS::IMaterial::Ptr material_;
SCENE_NS::IScene::Ptr scene_;
bool finally(napi_env env) override
{
napi_value args[] = {
this_.GetValue(), // scene..
args_.GetValue() // params.
};
if (type_ == BaseMaterial::SHADER) {
MakeNativeObjectParam(env, material_, BASE_NS::countof(args), args);
NapiApi::Object materialJS(GetJSConstructor(env, "ShaderMaterial"), BASE_NS::countof(args), args);
result = materialJS;
} else {
// fail..
material_.reset();
}
return true;
};
};
AsyncState* data = new AsyncState();
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
data->type_ = ctx.Arg<1>();
if (ctx.Arg<0>()) {
NapiApi::Object parms = ctx.Arg<0>();
data->name_ = parms.Get("name");
}
napi_value result = MakePromise(ctx, data);
data->scene_ = interface_pointer_cast(GetNativeObject());
// create an engine task and complete it there..
auto fun = [data]() {
data->material_ = data->scene_->CreateMaterial(data->name_);
data->CallIt();
return false;
};
META_NS::GetTaskQueueRegistry()
.GetTaskQueue(ENGINE_THREAD)
->AddTask(META_NS::MakeCallback(BASE_NS::move(fun)));
return result;
}
napi_value SceneJS::CreateShader(NapiApi::FunctionContext& ctx)
{
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
BASE_NS::string uri_;
BASE_NS::string name_;
SCENE_NS::IShader::Ptr shader_;
bool finally(napi_env env) override
{
napi_value args[] = {
this_.GetValue(), // scene..
args_.GetValue() // params.
};
NapiApi::Object parms(env, args[1]);
napi_value null;
napi_get_null(env, &null);
parms.Set("Material", null); // not bound to anything...
MakeNativeObjectParam(env, shader_, BASE_NS::countof(args), args);
NapiApi::Object shaderJS(GetJSConstructor(env, "Shader"), BASE_NS::countof(args), args);
result = shaderJS;
return true;
};
};
AsyncState* data = new AsyncState();
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
if (ctx.Arg<0>()) {
NapiApi::Object parms = ctx.Arg<0>();
data->name_ = parms.Get("name");
data->uri_ = FetchResourceOrUri(ctx, parms.Get("uri"));
}
napi_value result = MakePromise(ctx, data);
auto fun = [data]() {
auto& obr = META_NS::GetObjectRegistry();
auto doc = interface_cast(obr.GetDefaultObjectContext());
auto renderContext = doc->GetPropertyByName("RenderContext")->GetValue();
auto params =
interface_pointer_cast(META_NS::GetObjectRegistry().Create(META_NS::ClassId::Object));
params->AddProperty(META_NS::ConstructProperty(
"RenderContext", renderContext, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
params->AddProperty(META_NS::ConstructProperty(
"uri", data->uri_, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
data->shader_ = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::Shader, params);
data->CallIt();
return false;
};
META_NS::GetTaskQueueRegistry()
.GetTaskQueue(ENGINE_THREAD)
->AddTask(META_NS::MakeCallback(BASE_NS::move(fun)));
return result;
}
void SceneJS::StoreBitmap(BASE_NS::string_view uri, SCENE_NS::IBitmap::Ptr bitmap)
{
CORE_NS::UniqueLock lock(mutex_);
if (bitmap) {
bitmaps_[uri] = bitmap;
} else {
// setting null. releases.
bitmaps_.erase(uri);
}
}
SCENE_NS::IBitmap::Ptr SceneJS::FetchBitmap(BASE_NS::string_view uri)
{
CORE_NS::UniqueLock lock(mutex_);
auto it = bitmaps_.find(uri);
if (it != bitmaps_.end()) {
return it->second;
}
return {};
}
napi_value SceneJS::CreateImage(NapiApi::FunctionContext& ctx)
{
using namespace RENDER_NS;
struct AsyncState : public AsyncStateBase {
NapiApi::StrongRef this_;
NapiApi::StrongRef args_;
BASE_NS::shared_ptr renderContext_;
BASE_NS::string name_;
BASE_NS::string uri_;
SCENE_NS::IBitmap::Ptr bitmap_;
RenderHandleReference imageHandle_;
SceneJS* owner_;
CORE_NS::IImageLoaderManager::LoadResult imageLoadResult_;
bool cached_ { false };
bool finally(napi_env env) override
{
if (!bitmap_) {
// return the error string..
napi_create_string_utf8(env, imageLoadResult_.error, NAPI_AUTO_LENGTH, &result);
return false;
}
if (!cached_) {
// create the jsobject if we don't have one.
napi_value args[] = {
this_.GetValue(), // scene..
args_.GetValue() // params.
};
MakeNativeObjectParam(env, bitmap_, BASE_NS::countof(args), args);
owner_->StoreBitmap(uri_, BASE_NS::move(bitmap_));
NapiApi::Object imageJS(GetJSConstructor(env, "Image"), BASE_NS::countof(args), args);
result = imageJS;
}
return true;
};
};
AsyncState* data = new AsyncState();
data->owner_ = this;
data->this_ = NapiApi::StrongRef(ctx, ctx.This());
data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
NapiApi::Object args = ctx.Arg<0>();
if (args) {
if (auto n = args.Get("name")) {
data->name_ = n;
}
data->uri_ = FetchResourceOrUri(ctx, args.Get("uri"));
}
napi_value result = MakePromise(ctx, data);
if (auto bitmap = FetchBitmap(data->uri_)) {
// no aliasing.. so the returned bitmaps name is.. the old one.
// *fix*
// oh we have it already, no need to do anything in engine side.
data->cached_ = true;
data->bitmap_ = bitmap;
auto obj = interface_pointer_cast(data->bitmap_);
data->result = FetchJsObj(obj);
data->Success(ctx);
return result;
}
auto& obr = META_NS::GetObjectRegistry();
auto doc = interface_cast(obr.GetDefaultObjectContext());
data->renderContext_ =
interface_pointer_cast(doc->GetPropertyByName("RenderContext")->GetValue());
// create an IO task (to load the cpu data)
auto fun = [data]() -> META_NS::IAny::Ptr {
uint32_t imageLoaderFlags = CORE_NS::IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS;
auto& imageLoaderMgr = data->renderContext_->GetEngine().GetImageLoaderManager();
data->imageLoadResult_ = imageLoaderMgr.LoadImage(data->uri_, imageLoaderFlags);
return {};
};
// create final engine task (to create gpu resource)
auto fun2 = [data](const META_NS::IAny::Ptr&) -> META_NS::IAny::Ptr {
if (!data->imageLoadResult_.success) {
CORE_LOG_E("Could not load image asset: %s", data->imageLoadResult_.error);
data->bitmap_ = nullptr;
} else {
auto& gpuResourceMgr = data->renderContext_->GetDevice().GetGpuResourceManager();
RenderHandleReference imageHandle {};
GpuImageDesc gpuDesc = gpuResourceMgr.CreateGpuImageDesc(data->imageLoadResult_.image->GetImageDesc());
gpuDesc.usageFlags = CORE_IMAGE_USAGE_SAMPLED_BIT | CORE_IMAGE_USAGE_TRANSFER_DST_BIT;
if (gpuDesc.engineCreationFlags & EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_GENERATE_MIPS) {
gpuDesc.usageFlags |= CORE_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
gpuDesc.memoryPropertyFlags = CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
data->imageHandle_ = gpuResourceMgr.Create(data->uri_, gpuDesc, std::move(data->imageLoadResult_.image));
data->bitmap_ = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::Bitmap);
data->bitmap_->Uri()->SetValue(data->uri_);
data->bitmap_->SetRenderHandle(data->imageHandle_, { gpuDesc.width, gpuDesc.height });
}
data->CallIt();
return {};
};
// execute first step in io thread..
// second in engine thread.
// and the final in js thread (with threadsafefunction)
auto ioqueue = META_NS::GetTaskQueueRegistry().GetTaskQueue(IO_QUEUE);
auto enginequeue = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
ioqueue->AddWaitableTask(META_NS::MakeCallback(BASE_NS::move(fun)))
->Then(META_NS::MakeCallback(BASE_NS::move(fun2)), enginequeue);
return result;
}
napi_value SceneJS::GetAnimations(NapiApi::FunctionContext<>& ctx)
{
auto scene = interface_pointer_cast(GetThisNativeObject(ctx));
if (!scene) {
return ctx.GetUndefined();
}
BASE_NS::vector animRes;
ExecSyncTask([scene, &animRes]() {
animRes = scene->GetAnimations();
return META_NS::IAny::Ptr {};
});
napi_value tmp;
auto status = napi_create_array_with_length(ctx, animRes.size(), &tmp);
size_t i = 0;
napi_value args[] = { ctx.This() };
for (const auto& node : animRes) {
napi_value val;
val = CreateFromNativeInstance(
ctx, interface_pointer_cast(node), false, BASE_NS::countof(args), args);
status = napi_set_element(ctx, tmp, i++, val);
// disposables_[
}
return tmp;
}
void SceneJS::DisposeHook(uintptr_t token, NapiApi::Object obj)
{
disposables_[token] = { obj };
}
void SceneJS::ReleaseDispose(uintptr_t token)
{
auto it = disposables_.find(token);
if (it != disposables_.end()) {
it->second.Reset();
disposables_.erase(it->first);
}
}
void SceneJS::StrongDisposeHook(uintptr_t token, NapiApi::Object obj)
{
strongDisposables_[token] = { obj };
}
void SceneJS::ReleaseStrongDispose(uintptr_t token)
{
auto it = strongDisposables_.find(token);
if (it != strongDisposables_.end()) {
it->second.Reset();
strongDisposables_.erase(it->first);
}
}