* 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 "node.h"
#include <algorithm>
#include <scene/ext/intf_ecs_context.h>
#include <scene/ext/scene_utils.h>
#include <scene/ext/util.h>
#include <scene/interface/intf_application_context.h>
#include <scene/interface/intf_external_node.h>
#include <scene/interface/intf_scene_manager.h>
#include <scene/interface/resource/intf_exported_node.h>
#include <scene/interface/resource/types.h>
#include <base/math/matrix_util.h>
#include <meta/api/object_name.h>
#include <meta/base/interface_utils.h>
#include <meta/interface/resource/intf_owned_resource_groups.h>
#include "../component/node_component.h"
#include "../converting_value.h"
#include "../core/ecs.h"
#include "../resource/util.h"
SCENE_BEGIN_NAMESPACE()
namespace {
struct NodeFlagsConverter {
using SourceType = SCENE_NS::NodeFlags;
using TargetType = uint32_t;
SourceType ConvertToSource(META_NS::IAny&, const TargetType& v) const
{
return static_cast<SourceType>(v);
}
TargetType ConvertToTarget(const SourceType& v) const
{
return static_cast<TargetType>(v);
}
};
}
bool Node::InitDynamicProperty(const META_NS::IProperty::Ptr& p, BASE_NS::string_view path)
{
if (p && p->GetName() == "NodeFlags") {
if (auto att = GetSelf<META_NS::IAttach>()) {
META_NS::Property<uint32_t> ep{GetComponentProperty(att.get(), "NodeComponent", "NodeFlags")};
if (ep) {
return PushForwardingValueInstance(ep,
interface_cast<META_NS::IStackProperty>(p),
META_NS::IValue::Ptr(new ConvertingValue<NodeFlagsConverter>(ep)));
}
}
return false;
}
return Super::InitDynamicProperty(p, path);
}
bool Node::Build(const META_NS::IMetadata::Ptr& d)
{
return Super::Build(d);
}
void Node::Destroy()
{
if (startableHandler_) {
startableHandler_->StopAll(GetAttachmentContainer(false));
startableHandler_.reset();
}
Super::Destroy();
}
IScene::Ptr Node::GetScene() const
{
if (auto s = GetInternalScene()) {
return s->GetScene();
}
return nullptr;
}
Future<INode::Ptr> Node::GetParent(NodeTag) const
{
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([s, myEnt = GetEntity()] {
auto ent = s->GetEcsContext().GetParent(myEnt);
return CORE_NS::EntityUtil::IsValid(ent) ? s->FindNode(ent, {}) : nullptr;
});
}
return {};
}
bool Node::SetEcsObject(const IEcsObject::Ptr& obj)
{
if (Super::SetEcsObject(obj)) {
if (auto scene = GetInternalScene()) {
if (scene->GetOptions().enableStartables) {
if (!startableHandler_) {
startableHandler_ = BASE_NS::make_unique<StartableHandler>(GetSelf<INode>());
}
if (startableHandler_) {
startableHandler_->StartAll(StartableHandler::StartType::DEFERRED);
}
}
}
return true;
}
return false;
}
bool Node::Attach(const IObject::Ptr& attachment, const IObject::Ptr& dataContext)
{
if (Super::Attach(attachment, dataContext)) {
if (startableHandler_) {
startableHandler_->Start(attachment, StartableHandler::StartType::DEFERRED);
}
return true;
}
return false;
}
bool Node::Detach(const IObject::Ptr& attachment)
{
if (startableHandler_) {
startableHandler_->Stop(attachment);
}
return Super::Detach(attachment);
}
Future<BASE_NS::vector<INode::Ptr>> Node::GetChildren() const
{
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([this, s, weak = BASE_NS::weak_ptr{GetSelf()}] {
return weak.lock() ? s->GetChildren(object_) : BASE_NS::vector<INode::Ptr>{};
});
}
return {};
}
Future<bool> Node::RemoveChild(const INode::Ptr& child)
{
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([this, s, child, weak = BASE_NS::weak_ptr{GetSelf()}] {
return weak.lock() ? s->RemoveChild(object_, child) : false;
});
}
return {};
}
Future<bool> Node::AddChild(const INode::Ptr& child, size_t index)
{
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([this, s, child, index, weak = BASE_NS::weak_ptr{GetSelf()}] {
return weak.lock() ? s->AddChild(object_, child, index) : false;
});
}
return {};
}
Future<INode::Ptr> Node::Clone(BASE_NS::string_view nodeName, const INode::Ptr& parent)
{
if (parent && parent->GetScene() != GetScene()) {
CORE_LOG_E("Cannot clone a Node to a different Scene.");
return {};
}
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly(
[this, s, parent, weak = BASE_NS::weak_ptr{GetSelf()}, name = BASE_NS::string(nodeName)]() mutable {
auto self = weak.lock();
if (!self) {
return INode::Ptr{};
}
s->SyncProperties();
IEcsObject::Ptr parentObj = nullptr;
if (auto i = interface_cast<IEcsObjectAccess>(parent)) {
parentObj = i->GetEcsObject();
}
if (name.empty()) {
auto p = parent ? parent : GetParent().GetResult();
if (p) {
name = p->GetUniqueChildName(GetName()).GetResult();
}
}
auto res = CloneAsChild(*object_, parentObj);
auto node = CORE_NS::EntityUtil::IsValid(res.entity) ? s->FindNode(res.entity, {}) : nullptr;
if (auto named = interface_cast<META_NS::INamed>(node)) {
named->Name()->SetValue(name);
SyncPropertyDirect(s, named->Name()).GetResult();
}
return node;
});
}
return {};
}
BASE_NS::string Node::GetName() const
{
return NamedSceneObject::GetName();
}
Future<BASE_NS::string> Node::GetPath() const
{
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([this, weak = BASE_NS::weak_ptr{GetSelf()}] {
return weak.lock() ? object_->GetPath() : BASE_NS::string{};
});
}
return {};
}
BASE_NS::Math::Mat4X4 Node::GetTransformMatrix() const
{
return BASE_NS::Math::Trs(
META_ACCESS_PROPERTY_VALUE(Position), META_ACCESS_PROPERTY_VALUE(Rotation), META_ACCESS_PROPERTY_VALUE(Scale));
}
static CORE_NS::IResourceManager::Ptr GetResources(INode& node)
{
if (auto s = node.GetScene()) {
if (auto is = s->GetInternalScene()) {
if (auto c = is->GetContext()) {
return c->GetResources();
}
}
}
return nullptr;
}
static IExternalNode::Ptr AddExternalNodeAttachment(INode& node, BASE_NS::vector<CORE_NS::Entity> entities,
const CORE_NS::ResourceIdContext& id, META_NS::IResourceGroupHandle::Ptr handle)
{
IExternalNode::Ptr ext = META_NS::GetObjectRegistry().Create<IExternalNode>(ClassId::ExternalNode);
if (ext) {
if (auto att = interface_cast<META_NS::IAttach>(&node)) {
ext->SetResourceId(id);
ext->SetAddedEntities(BASE_NS::move(entities));
ext->SetSubresourceGroup(BASE_NS::move(handle));
att->Attach(ext);
}
}
return ext;
}
Future<INode::Ptr> Node::ImportChild(const INode::ConstPtr& node)
{
if (!node) {
return {};
}
if (node->GetScene() == GetScene()) {
CORE_LOG_W("Child already belongs to the target scene.");
return {};
}
IEcsObject::Ptr eobj;
if (auto i = interface_cast<IEcsObjectAccess>(node)) {
eobj = i->GetEcsObject();
}
if (eobj) {
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([this, s, eobj, node, weak = BASE_NS::weak_ptr{GetSelf()}] {
auto self = weak.lock();
if (!self) {
return INode::Ptr{};
}
auto res = CopyExternalAsChild(*object_, *eobj);
auto n = CORE_NS::EntityUtil::IsValid(res.entity) ? s->FindNode(res.entity, {}) : nullptr;
if (n && s->GetOptions().createResources) {
if (auto resource = interface_pointer_cast<CORE_NS::IResource>(node->GetScene())) {
AddExternalNodeAttachment(*n,
BASE_NS::move(res.newEntities),
{resource->GetResourceId(), resource->GetContext()},
{});
}
}
return n;
});
}
}
return {};
}
static META_NS::IResourceGroupHandle::Ptr GetSubGroup(const IInternalScene::Ptr& s, const BASE_NS::string& group)
{
META_NS::IResourceGroupHandle::Ptr handle;
if (auto c = s->GetContext()) {
if (auto i = interface_pointer_cast<META_NS::IOwnedResourceGroups>(c->GetResources())) {
handle = i->GetGroupHandle(group, s->GetScene());
}
}
return handle;
}
BASE_NS::string HandleName(const INode::Ptr& node, IInternalScene& scene, BASE_NS::string_view name,
INodeImport::ImportOptions::NamingBehavior behavior)
{
BASE_NS::string renamed(name);
switch (behavior) {
case INodeImport::ImportOptions::NamingBehavior::ENFORCE_UNIQUE_NAME:
renamed = scene.GetUniqueName(name, GetEcsObjectEntity(interface_cast<IEcsObjectAccess>(node)));
break;
case Scene::INodeImport::ImportOptions::NamingBehavior::REMOVE_EXISTING:
if (!name.empty() && scene.RemoveNamedChild(node, renamed)) {
CORE_LOG_W("Removed existing duplicate node when importing '%s'", renamed.c_str());
}
break;
case INodeImport::ImportOptions::NamingBehavior::NAME_AS_IS:
[[fallthrough]];
default:
break;
}
return renamed;
}
Future<INode::Ptr> Node::ImportChildScene(const IScene::ConstPtr& scene, const ImportOptions& options)
{
if (scene == GetScene()) {
CORE_LOG_E("Cannot import scene into itself.");
return {};
}
auto s = GetInternalScene();
if (!s || !scene) {
return {};
}
BASE_NS::string name(options.nodeName);
BASE_NS::string resourceGroup(options.resourceGroup);
auto namingBehavior = options.namingBehavior;
auto shareResources = options.shareResources;
return s->AddTaskOrRunDirectly([this,
s,
scene,
name,
resourceGroup,
namingBehavior,
shareResources,
weak = BASE_NS::weak_ptr{GetSelf()}]() mutable {
auto self = weak.lock();
if (!self) {
return INode::Ptr{};
}
name = HandleName(GetSelf<INode>(), *s, name, namingBehavior);
auto res = CopyExternalAsChild(*object_, *scene, resourceGroup, shareResources);
auto node = CORE_NS::EntityUtil::IsValid(res.entity) ? s->FindNode(res.entity, {}) : nullptr;
if (auto named = interface_cast<META_NS::INamed>(node)) {
named->Name()->SetValue(name);
SyncPropertyDirect(s, named->Name()).GetResult();
}
if (node && s->GetOptions().createResources) {
auto handle = GetSubGroup(s, res.resourceGroup);
if (auto resource = interface_pointer_cast<CORE_NS::IResource>(scene)) {
if (auto ext = AddExternalNodeAttachment(*node,
BASE_NS::move(res.newEntities),
{resource->GetResourceId(), resource->GetContext()},
handle)) {
ext->AddAssociatedResources(res.associatedResources);
}
}
}
return node;
});
}
Future<INode::Ptr> Node::ImportChildScene(BASE_NS::string_view uri, BASE_NS::string_view nodeName)
{
auto s = GetInternalScene();
if (!s) {
return {};
}
return s->AddTaskOrRunDirectly(
[this, s, weak = BASE_NS::weak_ptr{GetSelf()}, uri = BASE_NS::string(uri), name = BASE_NS::string(nodeName)] {
auto self = weak.lock();
if (!self) {
return INode::Ptr{};
}
if (auto provider = interface_cast<IApplicationContextProvider>(s)) {
if (auto appContext = provider->GetApplicationContext()) {
if (auto manager = appContext->GetSceneManager()) {
auto scene = manager->CreateScene(uri, s->GetOptions()).GetResult();
INodeImport::ImportOptions options;
options.nodeName = name;
options.namingBehavior = INodeImport::ImportOptions::NamingBehavior::NAME_AS_IS;
return ImportChildScene(scene, options).GetResult();
}
}
}
return INode::Ptr{};
});
}
static void AddNodeTemplateExternalNode(const IInternalScene::Ptr& s, INodeImportResult& nodeRes,
const META_NS::IObjectTemplate::ConstPtr& templ, const BASE_NS::string& group)
{
auto res = interface_pointer_cast<CORE_NS::IResource>(templ);
if (!res) {
return;
}
auto handle = GetSubGroup(s, group);
BASE_NS::vector<CORE_NS::Entity> entities;
if (auto resuMan = static_cast<IResourceComponentManager*>(s->GetEcsContext().FindComponent<ResourceComponent>())) {
for (auto&& r : nodeRes.GetNewResourcesImported()) {
auto ent = resuMan->GetEntity(r.id);
if (CORE_NS::EntityUtil::IsValid(ent)) {
entities.push_back(ent);
}
}
auto groupEnts = resuMan->GetEntities({CORE_NS::MatchingResourceId{group}});
entities.insert(entities.begin(), groupEnts.begin(), groupEnts.end());
}
if (auto ext = AddExternalNodeAttachment(
*nodeRes.GetNode(), entities, {res->GetResourceId(), res->GetContext()}, handle)) {
ext->AddAssociatedResources(nodeRes.GetAssociatedResources());
}
}
Future<INode::Ptr> Node::ImportTemplate(
const META_NS::IObjectTemplate::ConstPtr& templ, BASE_NS::string_view resourceGroup)
{
auto s = GetInternalScene();
if (!templ || !s) {
return {};
}
return s->AddTaskOrRunDirectly(
[s, templ, weak = BASE_NS::weak_ptr{GetSelf()}, rg = BASE_NS::string(resourceGroup)] {
auto self = weak.lock();
if (!self) {
return INode::Ptr{};
}
BASE_NS::string group = rg;
if (group.empty()) {
if (auto res = interface_pointer_cast<CORE_NS::IResource>(templ)) {
group = res->GetResourceId().name;
} else {
group = "Node Template";
}
}
CORE_LOG_D("template import group: %s", group.c_str());
INodeImportResult::Ptr nodeRes;
auto ntcontext = META_NS::GetObjectRegistry().Create<INodeTemplateContext>(ClassId::NodeTemplateContext);
if (!ntcontext) {
CORE_LOG_W("Invalid state");
return INode::Ptr{};
}
ntcontext->SetTargetNode(interface_pointer_cast<INode>(self));
ntcontext->SetGroup(group);
nodeRes = interface_pointer_cast<INodeImportResult>(templ->Instantiate(ntcontext));
bool isValid = nodeRes && nodeRes->GetNode();
if (isValid && s->GetOptions().createResources) {
AddNodeTemplateExternalNode(s, *nodeRes, templ, group);
}
return isValid ? nodeRes->GetNode() : nullptr;
});
}
template<typename Func>
static META_NS::IterationResult IterateImpl(const BASE_NS::vector<INode::Ptr>& cont, const Func& func)
{
for (auto&& child : cont) {
if (auto obj = interface_pointer_cast<META_NS::IObject>(child)) {
auto res = func->Invoke(obj);
if (!res.Continue()) {
return res;
}
}
}
return META_NS::IterationResult::CONTINUE;
}
META_NS::IterationResult Node::Iterate(const META_NS::IterationParameters& params)
{
auto f = params.function.GetInterface<META_NS::IIterableCallable<META_NS::IObject::Ptr>>();
if (!f) {
CORE_LOG_W("Incompatible function with Iterate");
return META_NS::IterationResult::FAILED;
}
return IterateImpl(GetChildren().GetResult(), f);
}
META_NS::IterationResult Node::Iterate(const META_NS::IterationParameters& params) const
{
auto f = params.function.GetInterface<META_NS::IIterableConstCallable<META_NS::IObject::Ptr>>();
if (!f) {
CORE_LOG_W("Incompatible function with Iterate");
return META_NS::IterationResult::FAILED;
}
return IterateImpl(GetChildren().GetResult(), f);
}
BASE_NS::vector<META_NS::IObject::Ptr> Node::GetAll() const
{
return META_NS::PtrArrayCast<IObject>(GetChildren().GetResult());
}
META_NS::IObject::Ptr Node::GetAt(SizeType index) const
{
auto children = GetChildren().GetResult();
return index < children.size() ? interface_pointer_cast<IObject>(children[index]) : nullptr;
}
META_NS::IContainer::SizeType Node::GetSize() const
{
return GetChildren().GetResult().size();
}
static bool MatchCriteria(const META_NS::IContainer::FindOptions& options, const META_NS::IObject::Ptr& object)
{
return object && (options.name.empty() || object->GetName() == options.name) &&
META_NS::CheckInterfaces(object, options.uids, options.strict);
}
BASE_NS::vector<META_NS::IObject::Ptr> Node::FindAll(const FindOptions& options) const
{
BASE_NS::vector<IObject::Ptr> res;
META_NS::Internal::ConstIterate(
this,
[&](const IObject::Ptr& obj) {
if (MatchCriteria(options, obj)) {
res.push_back(obj);
}
return true;
},
META_NS::IterateStrategy{options.behavior, META_NS::LockType::SHARED_LOCK});
return res;
}
META_NS::IObject::Ptr Node::FindAny(const FindOptions& options) const
{
IObject::Ptr res;
META_NS::Internal::ConstIterate(
this,
[&](const IObject::Ptr& obj) {
if (MatchCriteria(options, obj)) {
res = obj;
}
return !res;
},
META_NS::IterateStrategy{options.behavior, META_NS::LockType::SHARED_LOCK});
return res;
}
META_NS::IObject::Ptr Node::FindByName(BASE_NS::string_view name) const
{
return FindAny({BASE_NS::string(name), META_NS::TraversalType::NO_HIERARCHY});
}
bool Node::Add(const META_NS::IObject::Ptr& object)
{
if (auto n = interface_pointer_cast<INode>(object)) {
return AddChild(n).GetResult();
}
return false;
}
bool Node::Insert(SizeType index, const META_NS::IObject::Ptr& object)
{
if (auto n = interface_pointer_cast<INode>(object)) {
return AddChild(n, index).GetResult();
}
return false;
}
bool Node::Remove(SizeType index)
{
if (auto s = GetInternalScene()) {
return s->RunDirectlyOrInTask([&] {
auto obj = GetAt(index);
return obj && Remove(obj);
});
}
return false;
}
bool Node::Remove(const META_NS::IObject::Ptr& child)
{
if (auto n = interface_pointer_cast<INode>(child)) {
return RemoveChild(n).GetResult();
}
return false;
}
bool Node::Move(SizeType fromIndex, SizeType toIndex)
{
if (auto s = GetInternalScene()) {
return s->RunDirectlyOrInTask([&] {
auto obj = GetAt(fromIndex);
return Move(obj, toIndex);
});
}
return false;
}
bool Node::Move(const META_NS::IObject::Ptr& child, SizeType toIndex)
{
if (auto s = GetInternalScene()) {
return s->RunDirectlyOrInTask([&] { return child && Remove(child) && Insert(toIndex, child); });
}
return false;
}
bool Node::Replace(const META_NS::IObject::Ptr& child, const META_NS::IObject::Ptr& replaceWith, bool addAlways)
{
if (auto s = GetInternalScene()) {
return s->RunDirectlyOrInTask([&] {
auto vec = GetAll();
size_t index = 0;
for (; index < vec.size(); ++index) {
if (vec[index] == child) {
break;
}
}
if (index == vec.size()) {
if (addAlways) {
Add(replaceWith);
return true;
}
return false;
}
return Remove(index) && Insert(index, replaceWith);
});
}
return false;
}
void Node::RemoveAll()
{
if (auto s = GetInternalScene()) {
s->RunDirectlyOrInTask([&] {
for (auto&& c : GetAll()) {
Remove(c);
}
});
}
}
bool Node::IsAncestorOf(const META_NS::IObject::ConstPtr& object) const
{
if (auto s = GetInternalScene()) {
return s->RunDirectlyOrInTask([&] {
auto node = interface_pointer_cast<INode>(object);
auto self = GetSelf<INode>();
do {
if (node == self) {
return true;
}
if (node) {
node = node->GetParent().GetResult();
}
} while (node);
return false;
});
}
return false;
}
META_NS::IObject::Ptr Node::GetParent(ContainableTag) const
{
META_NS::IObject::Ptr res;
if (auto s = GetInternalScene()) {
res = interface_pointer_cast<META_NS::IObject>(GetParent(NodeTag{}).GetResult());
if (!res && GetEntity() == s->GetEcsContext().GetRootEntity()) {
res = interface_pointer_cast<META_NS::IObject>(GetScene());
}
}
return res;
}
void Node::OnMetadataConstructed(const META_NS::StaticMetadata& m, CORE_NS::IInterface& i)
{
if (BASE_NS::string_view("OnContainerChanged") == m.name) {
if (auto s = GetInternalScene()) {
s->RunDirectlyOrInTask([&] { s->ListenNodeChanges(true); });
}
}
}
void Node::OnChildChanged(META_NS::ContainerChangeType type, const INode::Ptr& child, size_t index)
{
if (auto event = GetEvent("OnContainerChanged", META_NS::MetadataQuery::EXISTING)) {
if (auto s = GetInternalScene()) {
META_NS::ChildChangedInfo info{
type, interface_pointer_cast<META_NS::IObject>(child), GetSelf<META_NS::IContainer>()};
if (type == META_NS::ContainerChangeType::ADDED) {
info.to = index;
}
if (type == META_NS::ContainerChangeType::REMOVED) {
info.from = index;
}
s->InvokeUserNotification<META_NS::IOnChildChanged>(event, info);
}
}
}
bool Node::IsListening() const
{
return GetEvent("OnContainerChanged", META_NS::MetadataQuery::EXISTING) != nullptr;
}
void Node::OnNodeActiveStateChanged(NodeActiteStateInfo state)
{
if (startableHandler_) {
if (state == NodeActiteStateInfo::ACTIVATED) {
startableHandler_->StartAll(StartableHandler::StartType::DEFERRED);
} else if (state == NodeActiteStateInfo::DEACTIVATING) {
startableHandler_->StopAll();
}
}
}
Future<bool> Node::IsEnabledInHierarchy() const
{
if (auto s = GetInternalScene()) {
return s->AddTaskOrRunDirectly([s, ent = GetEntity()] {
auto& ecs = s->GetEcsContext();
if (auto c =
static_cast<CORE3D_NS::INodeComponentManager*>(ecs.FindComponent<CORE3D_NS::NodeComponent>())) {
if (auto v = c->Read(ent)) {
return v->effectivelyEnabled;
}
}
return false;
});
}
return {};
}
Future<BASE_NS::string> Node::GetUniqueChildName(BASE_NS::string_view name) const
{
auto is = GetInternalScene();
const auto n = BASE_NS::string(name);
if (!is) {
return META_NS::GetTaskQueueRegistry().ConstructFutureWithValue(META_NS::ConstructAny(n));
}
return is->AddTaskOrRunDirectly([is, n, entity = GetEntity()] { return is->GetUniqueName(n, entity); });
}
SCENE_END_NAMESPACE()