/*
* 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 "BaseObjectJS.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);
#undef DECL_ENUM
exports.Set("NodeType", NodeType);
}
NodeImpl::NodeImpl(NodeType type) : SceneResourceImpl(SceneResourceImpl::NODE), type_(type)
{
LOG_F("NodeImpl ++");
}
NodeImpl::~NodeImpl()
{
LOG_F("NodeImpl --");
}
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);
// 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"));
// 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();
if (TrueRootObject* instance = GetThisRootObject(ctx)) {
instance->DisposeNative();
}
scene_.Reset();
return ctx.GetUndefined();
}
napi_value NodeImpl::GetLayerMaskEnabled(NapiApi::FunctionContext& ctx)
{
uint32_t bit = ctx.Arg<0>();
bool enabled = false;
if (auto node = interface_pointer_cast(GetThisNativeObject(ctx))) {
uint64_t mask = 1ull << bit;
ExecSyncTask([node, mask, &enabled]() {
enabled = node->LayerMask()->GetValue() & mask;
return META_NS::IAny::Ptr {};
});
}
return ctx.GetBoolean(enabled);
}
napi_value NodeImpl::SetLayerMaskEnabled(NapiApi::FunctionContext& ctx)
{
uint32_t bit = ctx.Arg<0>();
bool enabled = ctx.Arg<1>();
if (auto node = interface_pointer_cast(GetThisNativeObject(ctx))) {
uint64_t mask = 1ull << bit;
ExecSyncTask([node, enabled, mask]() {
if (enabled) {
node->LayerMask()->SetValue(node->LayerMask()->GetValue() | mask);
} else {
node->LayerMask()->SetValue(node->LayerMask()->GetValue() & ~mask);
}
return META_NS::IAny::Ptr {};
});
}
return ctx.GetUndefined();
}
napi_value NodeImpl::GetNodeType(NapiApi::FunctionContext<>& ctx)
{
uint32_t type = -1; // return -1 if the object does not exist anymore
if (auto node = interface_cast(GetThisNativeObject(ctx))) {
type = type_;
}
napi_value value;
napi_status status = napi_create_uint32(ctx, type, &value);
return value;
}
napi_value NodeImpl::GetLayerMask(NapiApi::FunctionContext<>& ctx)
{
if (auto node = interface_cast(GetThisNativeObject(ctx))) {
return ctx.This();
}
return ctx.GetUndefined();
}
napi_value NodeImpl::GetPath(NapiApi::FunctionContext<>& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
BASE_NS::string path;
if (node) {
ExecSyncTask([node, &path]() {
if (interface_cast(node)->GetParent()) {
path = node->Path()->GetValue();
}
return META_NS::IAny::Ptr {};
});
}
napi_value value;
napi_status status = napi_create_string_utf8(ctx, path.c_str(), path.length(), &value);
return value;
}
napi_value NodeImpl::GetVisible(NapiApi::FunctionContext<>& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
bool visible = false;
if (node) {
ExecSyncTask([node, &visible]() {
visible = node->Visible()->GetValue();
return META_NS::IAny::Ptr {};
});
}
napi_value value;
napi_status status = napi_get_boolean(ctx, visible, &value);
return value;
}
void NodeImpl::SetVisible(NapiApi::FunctionContext& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (node) {
bool visible = ctx.Arg<0>();
ExecSyncTask([node, visible]() {
node->Visible()->SetValue(visible);
return META_NS::IAny::Ptr {};
});
}
}
napi_value NodeImpl::GetPosition(NapiApi::FunctionContext<>& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return ctx.GetUndefined();
}
if (posProxy_ == nullptr) {
posProxy_ = BASE_NS::make_unique(ctx, node->Position());
}
return posProxy_ ? posProxy_->Value() : NapiApi::Value();
}
void NodeImpl::SetPosition(NapiApi::FunctionContext& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return;
}
NapiApi::Object obj = ctx.Arg<0>();
if (posProxy_ == nullptr) {
posProxy_ = BASE_NS::make_unique(ctx, node->Position());
}
posProxy_->SetValue(obj);
}
napi_value NodeImpl::GetScale(NapiApi::FunctionContext<>& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return ctx.GetUndefined();
}
if (sclProxy_ == nullptr) {
sclProxy_ = BASE_NS::make_unique(ctx, node->Scale());
}
return sclProxy_ ? sclProxy_->Value() : NapiApi::Value();
}
void NodeImpl::SetScale(NapiApi::FunctionContext& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return;
}
NapiApi::Object obj = ctx.Arg<0>();
if (sclProxy_ == nullptr) {
sclProxy_ = BASE_NS::make_unique(ctx, node->Scale());
}
sclProxy_->SetValue(obj);
}
napi_value NodeImpl::GetRotation(NapiApi::FunctionContext<>& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return ctx.GetUndefined();
}
if (rotProxy_ == nullptr) {
rotProxy_ = BASE_NS::make_unique(ctx, node->Rotation());
}
return rotProxy_ ? rotProxy_->Value() : NapiApi::Value();
}
void NodeImpl::SetRotation(NapiApi::FunctionContext& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return;
}
NapiApi::Object obj = ctx.Arg<0>();
if (rotProxy_ == nullptr) {
rotProxy_ = BASE_NS::make_unique(ctx, node->Rotation());
}
rotProxy_->SetValue(obj);
}
napi_value NodeImpl::GetParent(NapiApi::FunctionContext<>& ctx)
{
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (!node) {
return ctx.GetNull();
}
META_NS::IObject::Ptr root;
if (auto containable = interface_cast(node)) {
ExecSyncTask([containable, &root]() {
root = interface_pointer_cast(containable->GetParent());
return META_NS::IAny::Ptr {};
});
}
if (!root) {
// no parent.
return ctx.GetNull();
}
if (auto cached = FetchJsObj(root)) {
// always return the same js object.
return cached;
}
if (!GetNativeMeta(scene_.GetObject())) {
CORE_LOG_F("INVALID SCENE!");
}
// create new js object for the native node.
NapiApi::Object argJS(ctx);
napi_value args[] = { scene_.GetValue(), argJS };
return CreateFromNativeInstance(ctx, root, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
}
napi_value NodeImpl::GetChildContainer(NapiApi::FunctionContext<>& ctx)
{
// make sure the child list is up to date.
auto node = interface_pointer_cast(GetThisNativeObject(ctx));
if (node) {
ExecSyncTask([node]() {
node->BuildChildren(SCENE_NS::INode::BuildBehavior::NODE_BUILD_ONLY_DIRECT_CHILDREN);
return META_NS::IAny::Ptr {};
});
}
// Node implements Container
return ctx.This();
}
// container implementation
bool SkipNode(SCENE_NS::INode::Ptr node)
{
auto o = interface_cast(node);
auto classid = o->GetClassId();
// white list of nodes that are actual nodes..
if ((classid == SCENE_NS::ClassId::Camera) || (classid == SCENE_NS::ClassId::Light) ||
(classid == SCENE_NS::ClassId::Node)) {
return false;
}
return true;
}
napi_value NodeImpl::GetChild(NapiApi::FunctionContext& ctx)
{
uint32_t index = ctx.Arg<0>();
META_NS::IObject::Ptr child;
auto metaobj = GetThisNativeObject(ctx);
if (auto container = interface_cast(metaobj)) {
ExecSyncTask([container, index, &child]() {
auto data = container->GetAll();
int count = 0;
for (auto d : data) {
// Skip nodes that are not real "nodes"
if (SkipNode(d)) {
continue;
}
if (count == index) {
child = interface_pointer_cast(d);
break;
}
count++;
}
return META_NS::IAny::Ptr {};
});
}
if (!child) {
// return null
napi_value value;
napi_get_null(ctx, &value);
return value;
}
auto cached = FetchJsObj(child);
if (!cached) {
// was not cached yet, so recreate.
NapiApi::Object argJS(ctx);
auto scn = scene_.GetObject();
if (!GetNativeMeta(scene_.GetObject())) {
CORE_LOG_F("INVALID SCENE!");
}
napi_value args[] = { scene_.GetValue(), argJS };
cached =
CreateFromNativeInstance(ctx, child, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
}
return cached;
}
napi_value NodeImpl::GetCount(NapiApi::FunctionContext<>& ctx)
{
uint32_t count = 0;
auto metaobj = GetThisNativeObject(ctx);
if (auto container = interface_cast(metaobj)) {
ExecSyncTask([container, &count]() {
auto data = container->GetAll();
for (auto d : data) {
// Skip nodes that are not real "nodes"
if (SkipNode(d)) {
continue;
}
count++;
}
return META_NS::IAny::Ptr {};
});
}
napi_value value;
napi_status nstatus = napi_create_uint32(ctx, count, &value);
return value;
}
napi_value NodeImpl::AppendChild(NapiApi::FunctionContext& ctx)
{
NapiApi::Object childJS = ctx.Arg<0>();
if ((napi_value)childJS == nullptr) {
// okay. Invalid arg error?
return ctx.GetUndefined();
}
auto childNode = GetNativeMeta(childJS);
if (!childNode) {
return ctx.GetUndefined();
}
auto metaobj = GetThisNativeObject(ctx);
if (auto container = interface_cast(metaobj)) {
ExecSyncTask([container, childNode]() {
container->Add(childNode);
childNode->Visible()->SetValue(true);
return META_NS::IAny::Ptr {};
});
}
// 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 = GetRootObject(ctx, childJS)) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(nullptr, true);
tro->SetNativeObject(native, false);
native.reset();
}
}
return ctx.GetUndefined();
}
napi_value NodeImpl::InsertChildAfter(NapiApi::FunctionContext& ctx)
{
NapiApi::Object childJS = ctx.Arg<0>();
NapiApi::Object siblingJS = ctx.Arg<1>();
if ((napi_value)childJS == nullptr) {
// okay. Invalid arg error?
return ctx.GetUndefined();
}
auto childNode = GetNativeMeta(childJS);
if (!childNode) {
return ctx.GetUndefined();
}
SCENE_NS::INode::Ptr siblingNode;
if (siblingJS) {
siblingNode = GetNativeMeta(siblingJS);
}
auto metaobj = GetThisNativeObject(ctx);
if (auto container = interface_cast(metaobj)) {
ExecSyncTask([container, childNode, siblingNode]() {
if (siblingNode) {
auto data = container->GetAll();
int64_t index = 0;
for (auto d : data) {
index++;
if (d == siblingNode) {
// insert "after" the hit
container->Insert(index, childNode);
childNode->Visible()->SetValue(true);
return META_NS::IAny::Ptr {};
}
}
// did not find the sibling.. do nothing? add first? add last?
// for now add last..
container->Add(childNode);
childNode->Visible()->SetValue(true);
return META_NS::IAny::Ptr {};
}
// insert as first..
container->Insert(0, childNode);
childNode->Visible()->SetValue(true);
return META_NS::IAny::Ptr {};
});
}
// 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 = GetRootObject(ctx, childJS)) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(nullptr, true);
tro->SetNativeObject(native, false);
native.reset();
}
}
return ctx.GetUndefined();
}
void NodeImpl::ResetNativeObj(NapiApi::FunctionContext<>& ctx, NapiApi::Object& obj)
{
if (auto tro = GetRootObject(ctx, obj)) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(nullptr, false);
tro->SetNativeObject(native, true);
native.reset();
}
}
}
napi_value NodeImpl::RemoveChild(NapiApi::FunctionContext& ctx)
{
// okay. just detach from parent. (make parent invalid)
// and disable it.
NapiApi::Object childJS = ctx.Arg<0>();
if ((napi_value)childJS == nullptr) {
// okay. Invalid arg error?
return ctx.GetUndefined();
}
auto childNode = GetNativeMeta(childJS);
if (!childNode) {
return ctx.GetUndefined();
}
// make the js object keep a strong ref.
// (or give SceneJS ownership? and add dispose hook?)
if (auto tro = GetRootObject(ctx, childJS)) {
if (auto native = tro->GetNativeObject()) {
tro->SetNativeObject(nullptr, false);
tro->SetNativeObject(native, true);
native.reset();
}
}
auto metaobj = GetThisNativeObject(ctx);
if (auto container = interface_cast(metaobj)) {
ExecSyncTask([container, childNode]() {
container->Remove(childNode);
childNode->Visible()->SetValue(false);
return META_NS::IAny::Ptr {};
});
}
return ctx.GetUndefined();
}
napi_value NodeImpl::ClearChildren(NapiApi::FunctionContext<>& ctx)
{
auto metaobj = GetThisNativeObject(ctx);
BASE_NS::vector removedNodes;
if (auto container = interface_cast(metaobj)) {
ExecSyncTask([container, &removedNodes]() {
auto tmp = container->GetAll();
for (auto t : tmp) {
if (auto node = interface_pointer_cast(t)) {
if (SkipNode(node)) {
continue;
}
container->Remove(node);
node->Visible()->SetValue(false);
removedNodes.emplace_back(BASE_NS::move(node));
}
}
return META_NS::IAny::Ptr {};
});
for (auto node : removedNodes) {
if (auto cached = FetchJsObj(node)) {
ResetNativeObj(ctx, cached);
}
}
}
return ctx.GetUndefined();
}
SCENE_NS::INode::Ptr recurse_children(const SCENE_NS::INode::Ptr startNode, BASE_NS::string_view path)
{
SCENE_NS::INode::Ptr node = startNode;
while (node != nullptr) {
node->BuildChildren(SCENE_NS::INode::BuildBehavior::NODE_BUILD_ONLY_DIRECT_CHILDREN);
// see if
if (auto container = interface_cast(node)) {
auto pos = path.find('/', 0);
BASE_NS::string_view step = path.substr(0, pos);
node = interface_pointer_cast(container->FindByName(step));
if (node) {
if (pos == BASE_NS::string_view::npos) {
return node;
}
path = path.substr(pos + 1);
}
}
}
return {};
}
napi_value NodeImpl::GetNodeByPath(NapiApi::FunctionContext& ctx)
{
BASE_NS::string path = ctx.Arg<0>();
auto meta = GetThisNativeObject(ctx);
if (!meta) {
return ctx.GetNull();
}
META_NS::IObject::Ptr child;
if (auto node = interface_pointer_cast(meta)) {
ExecSyncTask([node, &child, path]() {
// make sure the child objects exist
child = interface_pointer_cast(recurse_children(node, path));
return META_NS::IAny::Ptr {};
});
}
if (!child) {
// no such child.
return ctx.GetNull();
}
if (auto cached = FetchJsObj(child)) {
// always return the same js object.
return cached;
}
if (!GetNativeMeta(scene_.GetObject())) {
CORE_LOG_F("INVALID SCENE!");
}
// create new js object for the native node.
NapiApi::Object argJS(ctx);
napi_value args[] = { scene_.GetValue(), argJS };
return CreateFromNativeInstance(ctx, child, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
}