/*
* 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 "NodeImpl.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include <3d/ecs/components/layer_component.h>
#include "BaseObjectJS.h"
#include "JsObjectCache.h"
void NodeImpl::RegisterEnums(NapiApi::Object exports)
{
napi_value v;
NapiApi::Object NodeType(exports.GetEnv());
#define DECL_ENUM(enu, x) \
{ \
napi_create_uint32(enu.GetEnv(), NodeImpl::NodeType::x, &v); \
enu.Set(#x, v); \
}
DECL_ENUM(NodeType, NODE);
DECL_ENUM(NodeType, GEOMETRY);
DECL_ENUM(NodeType, CAMERA);
DECL_ENUM(NodeType, LIGHT);
DECL_ENUM(NodeType, TEXT);
#undef DECL_ENUM
exports.Set("NodeType", NodeType);
}
NodeImpl::NodeImpl(NodeType type) : SceneResourceImpl(SceneResourceImpl::NODE), type_(type)
{
LOG_V("NodeImpl ++");
}
NodeImpl::~NodeImpl()
{
LOG_V("NodeImpl --");
posProxy_.reset();
sclProxy_.reset();
rotProxy_.reset();
}
void* NodeImpl::GetInstanceImpl(uint32_t id)
{
if (id == NodeImpl::ID)
return this;
return SceneResourceImpl::GetInstanceImpl(id);
}
void NodeImpl::GetPropertyDescs(BASE_NS::vector& props)
{
using namespace NapiApi;
// META_NS::IContainer;
SceneResourceImpl::GetPropertyDescs(props);
// override "name" from sceneresource
for (auto it = props.begin(); it != props.end(); it++) {
if (BASE_NS::string_view("name").compare(it->utf8name) == 0) {
props.erase(it);
break;
}
}
props.push_back(
TROGetSetProperty("name"));
// node
props.push_back(TROGetProperty("path"));
props.push_back(TROGetSetProperty("position"));
props.push_back(TROGetSetProperty("rotation"));
props.push_back(TROGetSetProperty("scale"));
props.push_back(TROGetProperty("parent"));
props.push_back(TROGetSetProperty("visible"));
props.push_back(TROGetSetProperty("children"));
props.push_back(TROGetProperty("nodeType"));
props.push_back(TROGetProperty("layerMask"));
// node methods
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::Dispose>("destroy"));
props.push_back(
MakeTROMethod, NodeImpl, &NodeImpl::GetNodeByPath>("getNodeByPath"));
props.push_back(
MakeTROMethod, NodeImpl, &NodeImpl::GetComponent>("getComponent"));
// layermask methods.
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::GetLayerMaskEnabled>("getEnabled"));
props.push_back(
MakeTROMethod, NodeImpl, &NodeImpl::SetLayerMaskEnabled>("setEnabled"));
// container
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::AppendChild>("append"));
props.push_back(
MakeTROMethod, NodeImpl, &NodeImpl::InsertChildAfter>("insertAfter"));
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::RemoveChild>("remove"));
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::GetChild>("get"));
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::ClearChildren>("clear"));
props.push_back(MakeTROMethod, NodeImpl, &NodeImpl::GetCount>("count"));
}
napi_value NodeImpl::Dispose(NapiApi::FunctionContext<>& ctx)
{
// Dispose of the native object. (makes the js object invalid)
posProxy_.reset();
sclProxy_.reset();
rotProxy_.reset();
SceneResourceImpl::Dispose(ctx);
scene_.Reset();
return ctx.GetUndefined();
}
napi_value NodeImpl::GetLayerMaskEnabled(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
uint32_t bit = ctx.Arg<0>();
bool enabled = true;
if (auto node = ctx.This().GetNative()) {
uint64_t mask = 1ull << bit;
if (auto comp = SCENE_NS::GetComponent(node)) {
enabled = comp->LayerMask()->GetValue() & mask;
}
}
return ctx.GetBoolean(enabled);
}
napi_value NodeImpl::SetLayerMaskEnabled(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
uint32_t bit = ctx.Arg<0>();
bool enabled = ctx.Arg<1>();
if (auto node = ctx.This().GetNative()) {
if (!SCENE_NS::GetComponent(node)) {
SCENE_NS::AddComponent(node);
}
if (auto layer = SCENE_NS::GetComponent(node)) {
uint64_t mask = 1ull << bit;
if (enabled) {
layer->LayerMask()->SetValue(layer->LayerMask()->GetValue() | mask);
} else {
layer->LayerMask()->SetValue(layer->LayerMask()->GetValue() & ~mask);
}
}
}
return ctx.GetUndefined();
}
napi_value NodeImpl::GetNodeType(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
uint32_t type = -1; // return -1 if the object does not exist anymore
if (ctx.This().GetNative()) {
type = type_;
}
return ctx.GetNumber(type);
}
napi_value NodeImpl::GetLayerMask(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
if (const auto nodeJs = ctx.This(); nodeJs.GetNative()) {
return nodeJs.ToNapiValue();
}
return ctx.GetUndefined();
}
napi_value NodeImpl::GetNodeName(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
BASE_NS::string name;
auto native = ctx.This().GetNative();
auto node = interface_pointer_cast(native);
if (!node) {
LOG_E("NodeImpl::GetNodeName NodeImpl not a node!");
return ctx.GetUndefined();
}
name = native->GetName();
// LEGACY COMPATIBILITY start
auto parent = node->GetParent().GetResult();
if (!parent) {
//parentless node, "root"
if (name.empty()) {
// no name, and no parent.
// so return the legacy default name
name = "rootNode_";
}
}
// LEGACY COMPATIBILITY end
return ctx.GetString(name);
}
void NodeImpl::SetNodeName(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
BASE_NS::string name = ctx.Arg<0>();
if (auto node = ctx.This().GetNative()) {
node->Name()->SetValue(name);
} else {
LOG_W("renaming resource not implemented, trying to name (%d) to '%s'", type_, name.c_str());
}
}
napi_value NodeImpl::GetPath(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto node = ctx.This().GetNative();
if (!node) {
// node does not exist anymore?
return ctx.GetUndefined();
}
auto parent = node->GetParent().GetResult();
if (!parent) {
// parentless nodes are "root", and root nodes path is ofcourse empty.
return ctx.GetString("");
}
BASE_NS::string path = node->GetPath().GetResult();
// LEGACY COMPATIBILITY start
// get root from scene..
NapiApi::Object obj = scene_.GetObject();
NapiApi::Object root = obj.Get("root");
// get the name of the root..
BASE_NS::string rootName = root.Get("name");
// remove node name from path.
path = path.substr(0, path.find_last_of('/') + 1);
// make sure root node name is there.. (hack)
// see if root name is in the path.
auto pos = path.find(rootName);
if (pos == BASE_NS::string::npos) {
// rootname missing from path.
path.insert(1, rootName.c_str());
}
// LEGACY COMPATIBILITY end
return ctx.GetString(path);
}
napi_value NodeImpl::GetVisible(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto node = ctx.This().GetNative();
bool visible = false;
if (node) {
visible = node->Enabled()->GetValue();
}
return ctx.GetBoolean(visible);
}
void NodeImpl::SetVisible(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
auto node = ctx.This().GetNative();
if (node) {
bool visible = ctx.Arg<0>();
node->Enabled()->SetValue(visible);
}
}
napi_value NodeImpl::GetPosition(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto node = ctx.This().GetNative();
if (!node) {
return ctx.GetUndefined();
}
if (posProxy_ == nullptr) {
posProxy_ = BASE_NS::make_unique(ctx.Env(), node->Position());
}
return posProxy_->Value();
}
void NodeImpl::SetPosition(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
auto node = ctx.This().GetNative();
if (!node) {
return;
}
NapiApi::Object obj = ctx.Arg<0>();
if (posProxy_ == nullptr) {
posProxy_ = BASE_NS::make_unique(ctx.Env(), node->Position());
}
posProxy_->SetValue(obj);
}
napi_value NodeImpl::GetScale(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto node = ctx.This().GetNative();
if (!node) {
return ctx.GetUndefined();
}
if (sclProxy_ == nullptr) {
sclProxy_ = BASE_NS::make_unique(ctx.Env(), node->Scale());
}
return sclProxy_->Value();
}
void NodeImpl::SetScale(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
auto node = ctx.This().GetNative();
if (!node) {
return;
}
NapiApi::Object obj = ctx.Arg<0>();
if (sclProxy_ == nullptr) {
sclProxy_ = BASE_NS::make_unique(ctx.Env(), node->Scale());
}
sclProxy_->SetValue(obj);
}
napi_value NodeImpl::GetRotation(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto node = ctx.This().GetNative();
if (!node) {
return ctx.GetUndefined();
}
if (rotProxy_ == nullptr) {
rotProxy_ = BASE_NS::make_unique(ctx.Env(), node->Rotation());
}
return rotProxy_->Value();
}
void NodeImpl::SetRotation(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
auto node = ctx.This().GetNative();
if (!node) {
return;
}
NapiApi::Object obj = ctx.Arg<0>();
if (rotProxy_ == nullptr) {
rotProxy_ = BASE_NS::make_unique(ctx.Env(), node->Rotation());
}
rotProxy_->SetValue(obj);
}
napi_value NodeImpl::GetParent(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto node = ctx.This().GetNative();
if (!node) {
return ctx.GetNull();
}
auto parent = node->GetParent().GetResult();
if (!parent) {
// no parent. (root node)
return ctx.GetNull();
}
if (!scene_.GetObject().GetNative()) {
LOG_F("INVALID SCENE!");
}
NapiApi::Env env(ctx.GetEnv());
NapiApi::Object argJS(env);
napi_value args[] = { scene_.GetValue(), argJS.ToNapiValue() };
// These are owned by the scene, so store only a weak reference.
auto js = CreateFromNativeInstance(env, parent, PtrType::WEAK, args);
if (auto wrapper = js.GetJsWrapper()) {
wrapper->Attached(true);
}
return js.ToNapiValue();
}
napi_value NodeImpl::GetChildContainer(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
// Node implements Container
return ctx.This().ToNapiValue();
}
napi_value NodeImpl::GetChild(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
LOG_F("INVALID SCENE!");
return ctx.GetUndefined();
}
uint32_t index = ctx.Arg<0>();
if (auto node = ctx.This().GetNative()) {
auto children = node->GetChildren().GetResult();
if (index < children.size()) {
const auto env = ctx.GetEnv();
auto argJS = NapiApi::Object { env };
napi_value args[] = { scene_.GetValue(), argJS.ToNapiValue() };
// These are owned by the scene, so store only weak reference.
auto js = CreateFromNativeInstance(env, children[index], PtrType::WEAK, args);
if (auto wrapper = js.GetJsWrapper()) {
wrapper->Attached(true);
}
return js.ToNapiValue();
}
}
return ctx.GetNull();
}
napi_value NodeImpl::GetCount(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
uint32_t count = 0;
if (auto node = ctx.This().GetNative()) {
count = node->GetChildren().GetResult().size();
}
return ctx.GetNumber(count);
}
napi_value NodeImpl::AppendChild(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto arg0 = ctx.Arg<0>();
if (arg0.IsUndefinedOrNull()) {
// okay. Invalid arg error?
return ctx.GetUndefined();
}
NapiApi::Object childJS = arg0;
auto childNode = childJS.GetNative();
if (!childNode) {
return ctx.GetUndefined();
}
if (auto parent = ctx.This().GetNative()) {
if (auto wrapper = childJS.GetJsWrapper()) {
wrapper->Attached(true);
}
parent->AddChild(childNode);
childNode->Enabled()->SetValue(true);
}
// make the js object keep a weak ref again (scene keeps the native object alive)
// (or move ownership back from SceneJS? and remove dispose hook?)
if (auto tro = childJS.GetRoot()) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(native, PtrType::WEAK);
}
}
return ctx.GetUndefined();
}
napi_value NodeImpl::InsertChildAfter(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
auto arg0 = ctx.Arg<0>();
if (arg0.IsUndefinedOrNull()) {
// okay. Invalid arg error?
return ctx.GetUndefined();
}
NapiApi::Object childJS = arg0;
auto childNode = childJS.GetNative();
if (!childNode) {
return ctx.GetUndefined();
}
auto arg1 = ctx.Arg<1>();
SCENE_NS::INode::Ptr siblingNode;
if (arg1.IsDefinedAndNotNull()) {
NapiApi::Object siblingJS = arg1;
siblingNode = siblingJS.GetNative();
}
if (auto parent = ctx.This().GetNative()) {
size_t index = 0;
if (siblingNode) {
auto data = parent->GetChildren().GetResult();
for (auto d : data) {
index++;
if (d == siblingNode) {
break;
}
}
}
if (auto wrapper = childJS.GetJsWrapper()) {
wrapper->Attached(true);
}
parent->AddChild(childNode, index).GetResult();
childNode->Enabled()->SetValue(true);
}
// make the js object keep a weak ref again (scene keeps the native object alive)
// (or move ownership back from SceneJS? and remove dispose hook?)
if (auto tro = childJS.GetRoot()) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(native, PtrType::WEAK);
}
}
return ctx.GetUndefined();
}
napi_value NodeImpl::RemoveChild(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
// okay. just detach from parent. (make parent invalid)
// and disable it.
auto arg0 = ctx.Arg<0>();
if (arg0.IsUndefinedOrNull()) {
// okay. Invalid arg error?
return ctx.GetUndefined();
}
NapiApi::Object childJS = arg0;
auto childNode = childJS.GetNative();
if (!childNode) {
return ctx.GetUndefined();
}
// make the js object keep a strong ref.
// (or give SceneJS ownership? and add dispose hook?)
if (auto tro = childJS.GetRoot()) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(native, PtrType::STRONG);
}
}
if (auto parent = ctx.This().GetNative()) {
if (auto wrapper = childJS.GetJsWrapper()) {
wrapper->Attached(false);
}
parent->RemoveChild(childNode).GetResult();
childNode->Enabled()->SetValue(false);
}
return ctx.GetUndefined();
}
napi_value NodeImpl::ClearChildren(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
BASE_NS::vector removedNodes;
if (auto parent = ctx.This().GetNative()) {
for (auto node : parent->GetChildren().GetResult()) {
if (auto childJS = FetchJsObj(node)) {
if (auto wrapper = childJS.GetJsWrapper()) {
wrapper->Attached(false);
}
}
parent->RemoveChild(node).GetResult();
node->Enabled()->SetValue(false);
removedNodes.emplace_back(BASE_NS::move(node));
}
for (auto node : removedNodes) {
if (auto cached = FetchJsObj(node)) {
if (auto tro = cached.GetRoot()) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(native, PtrType::STRONG);
}
}
}
}
}
return ctx.GetUndefined();
}
napi_value NodeImpl::GetNodeByPath(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
LOG_F("INVALID SCENE!");
return ctx.GetUndefined();
}
BASE_NS::string path = ctx.Arg<0>();
if (auto node = ctx.This().GetNative()) {
BASE_NS::string childPath = node->GetPath().GetResult() + "/" + path;
const auto child = node->GetScene()->FindNode(childPath).GetResult();
if (!child) {
return ctx.GetNull();
}
const auto env = ctx.GetEnv();
auto argJS = NapiApi::Object { env };
napi_value args[] = { scene_.GetValue(), argJS.ToNapiValue() };
// These are owned by the scene, so store only weak reference.
auto js = CreateFromNativeInstance(env, child, PtrType::WEAK, args);
if (js.GetJsWrapper()) {
js.GetJsWrapper()->Attached(true);
return js.ToNapiValue();
}
}
return ctx.GetNull();
}
napi_value NodeImpl::GetComponent(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
BASE_NS::string name = ctx.Arg<0>();
auto meta = ctx.This().GetNative();
if (!meta) {
return ctx.GetNull();
}
if (auto att = interface_pointer_cast(meta)) {
if (auto cont = att->GetAttachmentContainer(false)) {
if (auto comp = cont->FindByName(name)) {
NapiApi::Env env(ctx.GetEnv());
NapiApi::Object argJS(env);
napi_value args[] = { scene_.GetValue(), argJS.ToNapiValue() };
return CreateFromNativeInstance(env, comp, PtrType::WEAK, args).ToNapiValue();
}
}
}
return ctx.GetUndefined();
}
bool NodeImpl::IsAttached() const
{
return attached_;
}
void NodeImpl::Attached(bool attached)
{
attached_ = attached;
}