/*
* 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 "BaseObjectJS.h"
#include
#include
#include
#include
// this class is used to store a reference to a JS object in the metaobject.
class JSWrapperState : public CORE_NS::IInterface {
public:
static constexpr BASE_NS::Uid UID { "2ef39765-91f2-46c4-b85f-7cad40dd3bcd" };
const IInterface* GetInterface(const BASE_NS::Uid& uid) const override;
IInterface* GetInterface(const BASE_NS::Uid& uid) override;
void Ref() override;
void Unref() override;
JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name);
~JSWrapperState();
NapiApi::Object GetObject();
private:
BASE_NS::string name_;
volatile int32_t count_ { 0 };
napi_env env_;
napi_ref ref_;
};
const CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const
{
if (uid == CORE_NS::IInterface::UID) {
return this;
}
if (uid == JSWrapperState::UID) {
return this;
}
return nullptr;
}
CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid)
{
if (uid == CORE_NS::IInterface::UID) {
return this;
}
if (uid == JSWrapperState::UID) {
return this;
}
return nullptr;
}
void JSWrapperState::Ref()
{
CORE_NS::AtomicIncrement(&count_);
}
void JSWrapperState::Unref()
{
if (CORE_NS::AtomicDecrement(&count_) == 0) {
delete this;
}
}
JSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name)
{
name_ = name;
LOG_F("JSWrapperState ++ %s", name_.c_str());
env_ = obj.GetEnv();
// Create a WEAK reference to the object
napi_create_reference(env_, obj, 0, &ref_);
}
JSWrapperState::~JSWrapperState()
{
// release the reference.
napi_delete_reference(env_, ref_);
LOG_F("JSWrapperState -- %s", name_.c_str());
}
NapiApi::Object JSWrapperState::GetObject()
{
napi_value value;
napi_get_reference_value(env_, ref_, &value);
return { env_, value };
}
TrueRootObject::TrueRootObject() {}
void TrueRootObject::SetNativeObject(META_NS::IObject::Ptr real, bool strong)
{
if (strong) {
obj_ = real;
} else {
objW_ = real;
}
}
META_NS::IObject::Ptr TrueRootObject::GetNativeObject()
{
// if we have a strong ref...
if (obj_) {
// return that directly.
return obj_;
}
// otherwise try to lock the weak reference.
return objW_.lock();
}
void TrueRootObject::Finalize(napi_env env)
{
// Synchronously destroy the lume object in engine thread.. (only for strong refs.)
if (obj_) {
ExecSyncTask([obj = BASE_NS::move(obj_)]() { return META_NS::IAny::Ptr {}; });
}
// and reset the weak ref too. (which may be null anyway)
objW_.reset();
}
NapiApi::Function GetJSConstructor(napi_env env, const BASE_NS::string_view jsName)
{
NapiApi::MyInstanceState* mis;
napi_get_instance_data(env, (void**)&mis);
return NapiApi::Function(env, mis->FetchCtor(jsName));
}
NapiApi::Object CreateJsObj(napi_env env, const BASE_NS::string_view jsName, META_NS::IObject::Ptr real, bool strong,
uint32_t argc, napi_value* argv)
{
NapiApi::Object obj(GetJSConstructor(env, jsName), argc, argv);
if (!obj) {
return {};
}
auto oo = GetRootObject(env, obj);
oo->SetNativeObject(real, strong);
return obj;
}
bool IsInstanceOf(const NapiApi::Object& obj, const BASE_NS::string_view jsName)
{
auto env = obj.GetEnv();
auto cl = GetJSConstructor(env, jsName);
bool result = false;
auto status = napi_instanceof(env, obj, cl, &result);
return result;
}
NapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj)
{
using namespace META_NS;
// access hidden property.
if (auto AppMeta = interface_pointer_cast(obj)) {
if (auto wrapper = AppMeta->GetPropertyByName("_JSW")) {
// The native object already contains a JS object.
return interface_cast(wrapper->GetValue())->GetObject();
}
}
return nullptr;
}
NapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, NapiApi::Object jsobj)
{
using namespace META_NS;
if (auto AppMeta = interface_pointer_cast(obj)) {
// Add a reference to the JS object to the native object.
auto res = BASE_NS::shared_ptr(new JSWrapperState(jsobj, obj->GetClassName()));
auto& obr = GetObjectRegistry();
auto wrapper = AppMeta->GetPropertyByName("_JSW");
// check if the property exists.
if (wrapper) {
// .. does the wrapper exist? (ie. reference from native to js)
if (auto val = interface_cast(wrapper->GetValue())) {
// validate it
auto ref = val->GetObject();
bool equals = false;
if (ref) {
// we have ref.. so make the compare here
napi_strict_equals(jsobj.GetEnv(), jsobj, ref, &equals);
if (!equals) {
// this may be a problem
// (there should only be a 1-1 mapping between js objects and native objects)
CORE_LOG_F("_JSW exists and points to different object!");
} else {
// objects already linked
return ref;
}
} else {
// creating a new jsobject for existing native object that was bound before (but js object was GC:d)
// this is fine.
CORE_LOG_V(
"Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)");
}
}
} else {
// create the wrapper property
wrapper = ConstructProperty(
"_JSW", nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE);
AppMeta->AddProperty(wrapper);
}
// link native to js.
wrapper->SetValue(interface_pointer_cast(res));
return res->GetObject();
}
napi_value val;
napi_get_null(jsobj.GetEnv(), &val);
return { jsobj.GetEnv(), val };
}
NapiApi::Object CreateFromNativeInstance(
napi_env env, const META_NS::IObject::Ptr& obj, bool strong, uint32_t argc, napi_value* argv)
{
napi_value null;
napi_get_null(env, &null);
if (obj == nullptr) {
return {};
}
using namespace META_NS;
NapiApi::Object nodeJS = FetchJsObj(obj);
if (nodeJS) {
// we have a cached js object for this native object
return nodeJS;
}
// no js object. create it.
BASE_NS::string name { obj->GetClassName() };
// specialize/remap class names & interfaces.
if (name == "Bitmap") {
name = "Image";
} else if (name == "Tonemap") {
name = "ToneMappingSettings";
} else if (name == "PostProcess") {
name = "PostProcessSettings";
} else if (name == "Material") {
// okay. specialize then...
SCENE_NS::IMaterial* mat = interface_cast(obj);
auto shdr = mat->MaterialShader()->GetValue();
auto shdruri = shdr->Uri()->GetValue();
if (!shdruri.empty()) {
name = "ShaderMaterial";
} else {
// hide other material types..
return {};
}
} else if (name == "Shader") {
// possible specialization?
name = "Shader";
} else if (name == "Node") {
if (interface_cast(obj)->GetMesh()) {
name = "Geometry";
} else {
name = "Node";
}
}
MakeNativeObjectParam(env, obj, argc, argv);
nodeJS = CreateJsObj(env, name, obj, strong, argc, argv);
if (!nodeJS) {
// EEK. could not create the object.
CORE_LOG_E("Could not create JSObject for Class %s", name.c_str());
return {};
}
return StoreJsObj(obj, nodeJS);
}