* Copyright (c) 2026 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 "resource_template_base.h"
#include <scene/ext/scene_utils.h>
#include <scene/interface/resource/intf_resource_context.h>
#include <meta/api/metadata_util.h>
#include <meta/api/util.h>
#include <meta/interface/intf_metadata.h>
#include <meta/interface/intf_object_flags.h>
#include <meta/interface/property/intf_stack_property.h>
#include <meta/interface/resource/intf_resource.h>
#include "template_copy_helpers.h"
SCENE_BEGIN_NAMESPACE()
static constexpr uint64_t IMPORTED_FROM_TEMPLATE_BIT = 128;
static void PromoteProperty(const META_NS::IProperty::Ptr& prop)
{
META_NS::PropertyLock lock{prop};
if (lock) {
if (auto sc = interface_cast<META_NS::IStackProperty>(prop.get())) {
sc->SetDefaultValue(prop->GetValue());
}
prop->ResetValue();
}
}
static void PromoteMetadata(META_NS::IMetadata& meta);
static bool IsArrayOfSubObjects(const META_NS::IProperty::ConstPtr& prop);
META_NS::IMetadata* GetArrayElementMetadata(const META_NS::IArrayAny& arr, size_t index)
{
auto itemAny = arr.Clone({META_NS::CloneValueType::DEFAULT_VALUE, META_NS::TypeIdRole::ITEM});
if (!itemAny) {
return nullptr;
}
if (!arr.GetAnyAt(index, *itemAny)) {
return nullptr;
}
auto ptr = META_NS::GetPointer(*itemAny);
return interface_cast<META_NS::IMetadata>(ptr.get());
}
static void PromoteArrayElements(const META_NS::IProperty::Ptr& prop)
{
auto arr = interface_cast<META_NS::IArrayAny>(&prop->GetValue());
if (!arr) {
return;
}
for (size_t i = 0; i < arr->GetSize(); ++i) {
if (auto m = GetArrayElementMetadata(*arr, i)) {
PromoteMetadata(*m);
}
}
}
static void PromoteReadonlyProperty(const META_NS::IProperty::Ptr& prop)
{
if (auto sub = META_NS::GetPointer<META_NS::IMetadata>(prop)) {
PromoteMetadata(*sub);
} else {
PromoteArrayElements(prop);
}
}
static bool IsOwnedSubObject(const META_NS::IProperty::ConstPtr& prop)
{
auto sub = META_NS::GetPointer<META_NS::IMetadata>(prop);
if (!sub) {
return false;
}
return interface_cast<CORE_NS::IResource>(sub) == nullptr;
}
static void PromoteMetadata(META_NS::IMetadata& meta)
{
for (auto&& p : meta.GetProperties()) {
const bool isSubObject = IsArrayOfSubObjects(p) || IsOwnedSubObject(p);
if (meta.GetMetadata(META_NS::MetadataType::PROPERTY, p->GetName()).readOnly || isSubObject) {
PromoteReadonlyProperty(p);
} else if (p->IsValueSet()) {
PromoteProperty(p);
}
}
}
static void CopyDefaultToDefault(const META_NS::IProperty::ConstPtr& src, META_NS::IProperty& dst)
{
META_NS::PropertyLock plock{src};
META_NS::PropertyLock lock{&dst};
if (!plock || !lock) {
return;
}
auto srcStack = interface_cast<META_NS::IStackProperty>(src.get());
auto dstStack = interface_cast<META_NS::IStackProperty>(&dst);
if (srcStack && dstStack) {
dstStack->SetDefaultValue(srcStack->GetDefaultValue());
}
dst.ResetValue();
}
static bool IsArrayOfSubObjects(const META_NS::IProperty::ConstPtr& prop)
{
auto arr = interface_cast<META_NS::IArrayAny>(&prop->GetValue());
if (!arr || arr->GetSize() == 0) {
return false;
}
return GetArrayElementMetadata(*arr, 0) != nullptr;
}
static bool NeedsDeepCopy(const META_NS::IMetadata& meta, const META_NS::IProperty::ConstPtr& prop)
{
if (meta.GetMetadata(META_NS::MetadataType::PROPERTY, prop->GetName()).readOnly) {
return true;
}
if (IsArrayOfSubObjects(prop)) {
return true;
}
return IsOwnedSubObject(prop);
}
static void CopyMetadataProperty(META_NS::IMetadata& dst, const META_NS::IProperty::ConstPtr& srcProp, CopyMode mode)
{
auto dstProp = dst.GetProperty(srcProp->GetName());
if (!dstProp) {
return;
}
if (NeedsDeepCopy(dst, srcProp)) {
CopyReadonlyProperty(srcProp, dstProp, mode);
} else {
CopyProperty(srcProp, *dstProp, mode);
}
}
void CopyToDefaultMaybeDeep(META_NS::IMetadata& dst, const META_NS::IProperty::ConstPtr& srcProp)
{
auto dstProp = dst.GetProperty(srcProp->GetName());
if (dstProp && NeedsDeepCopy(dst, srcProp)) {
CopyReadonlyProperty(srcProp, dstProp, CopyMode::WITH_DEFAULTS);
} else {
META_NS::CopyToDefaultAndReset(srcProp, dst);
}
}
void CopyArrayElements(
const META_NS::IProperty::ConstPtr& srcProp, const META_NS::IProperty::Ptr& dstProp, CopyMode mode)
{
auto srcArr = interface_cast<META_NS::IArrayAny>(&srcProp->GetValue());
auto dstArr = interface_cast<META_NS::IArrayAny>(&dstProp->GetValue());
if (!srcArr || !dstArr) {
return;
}
const auto count = srcArr->GetSize();
for (size_t i = 0; i < count && i < dstArr->GetSize(); ++i) {
auto srcMeta = GetArrayElementMetadata(*srcArr, i);
auto dstMeta = GetArrayElementMetadata(*dstArr, i);
if (srcMeta && dstMeta) {
CopyMetadata(*srcMeta, *dstMeta, mode);
}
}
}
void CopyReadonlyProperty(
const META_NS::IProperty::ConstPtr& srcProp, const META_NS::IProperty::Ptr& dstProp, CopyMode mode)
{
auto srcMeta = META_NS::GetPointer<META_NS::IMetadata>(srcProp);
auto dstMeta = META_NS::GetPointer<META_NS::IMetadata>(dstProp);
if (srcMeta && dstMeta) {
CopyMetadata(*srcMeta, *dstMeta, mode);
} else {
CopyArrayElements(srcProp, dstProp, mode);
}
}
void CopyProperty(const META_NS::IProperty::ConstPtr& srcProp, META_NS::IProperty& dstProp, CopyMode mode)
{
if (mode == CopyMode::WITH_DEFAULTS) {
CopyDefaultToDefault(srcProp, dstProp);
}
if (srcProp->IsValueSet()) {
META_NS::CopyValue(srcProp, dstProp);
}
}
void CopyMetadata(const META_NS::IMetadata& src, META_NS::IMetadata& dst, CopyMode mode)
{
CopyMetadataExcept(src, dst, mode, {});
}
static bool IsExcludedName(std::initializer_list<BASE_NS::string_view> skipNames, BASE_NS::string_view name)
{
for (auto&& skip : skipNames) {
if (!skip.empty() && name == skip) {
return true;
}
}
return false;
}
void CopyMetadataExcept(const META_NS::IMetadata& src, META_NS::IMetadata& dst, CopyMode mode,
std::initializer_list<BASE_NS::string_view> skipNames)
{
for (auto&& srcProp : src.GetProperties()) {
const bool excluded = IsExcludedName(skipNames, srcProp->GetName());
const bool isSubObject = !excluded && (IsArrayOfSubObjects(srcProp) || IsOwnedSubObject(srcProp));
const bool shouldCopy = !excluded && (mode == CopyMode::WITH_DEFAULTS || srcProp->IsValueSet() || isSubObject);
if (shouldCopy) {
auto dstProp = dst.GetProperty(srcProp->GetName());
if (dstProp) {
CopyMetadataProperty(dst, srcProp, mode);
} else if (mode == CopyMode::OVERRIDES_ONLY && srcProp->IsValueSet()) {
META_NS::Clone(srcProp, dst);
}
}
}
}
void CopyPropertyIfSet(const META_NS::IProperty::ConstPtr& src, const META_NS::IProperty::Ptr& dst)
{
if (src && dst && src->IsValueSet()) {
META_NS::CopyValue(src, *dst);
}
}
void ClonePropertyWithDefaults(const META_NS::IProperty::ConstPtr& src, META_NS::IMetadata& dst)
{
META_NS::PropertyLock lock{src};
if (!lock) {
return;
}
auto copy = META_NS::DuplicatePropertyType(META_NS::GetObjectRegistry(), src);
if (!copy) {
return;
}
auto srcStack = interface_cast<META_NS::IStackProperty>(src.get());
auto dstStack = interface_cast<META_NS::IStackProperty>(copy.get());
if (srcStack && dstStack) {
dstStack->SetDefaultValue(srcStack->GetDefaultValue());
}
if (src->IsValueSet()) {
copy->SetValue(src->GetValue());
}
dst.AddProperty(copy);
}
static void ResetSetProperties(META_NS::IMetadata& meta)
{
for (auto&& md : meta.GetAllMetadatas(META_NS::MetadataType::PROPERTY)) {
auto p = meta.GetProperty(md.name);
if (p && p->IsValueSet()) {
META_NS::PropertyLock lock{p};
if (lock) {
p->ResetValue();
}
}
}
}
static void CopyIntoReadonlySubObjects(const META_NS::IMetadata& src, META_NS::IMetadata& dst)
{
for (auto&& srcProp : src.GetProperties()) {
auto dstProp = dst.GetProperty(srcProp->GetName());
if (dstProp && NeedsDeepCopy(dst, srcProp)) {
auto srcMeta = META_NS::GetPointer<META_NS::IMetadata>(srcProp);
auto dstMeta = META_NS::GetPointer<META_NS::IMetadata>(dstProp);
if (srcMeta && dstMeta) {
META_NS::CopyValue(*srcMeta, *dstMeta);
}
}
}
}
static CORE_NS::IResourceManager::Ptr GetResourceManagerFromContext(const CORE_NS::ResourceContextPtr& context)
{
if (auto rcontext = interface_cast<IResourceContext>(context)) {
return GetResourceManager(rcontext->GetScene());
}
if (auto scene = interface_pointer_cast<IScene>(context)) {
return GetResourceManager(scene);
}
return nullptr;
}
static void DefaultApplyBaseDefaults(META_NS::IObject& target, const META_NS::IMetadata& base)
{
auto dst = interface_cast<META_NS::IMetadata>(&target);
if (!dst) {
return;
}
for (auto&& md : base.GetAllMetadatas(META_NS::MetadataType::PROPERTY)) {
if (auto p = base.GetProperty(md.name)) {
CopyToDefaultMaybeDeep(*dst, p);
}
}
}
BASE_NS::string ResourceTemplateBase::GetName() const
{
const META_NS::IMetadata& meta = *this;
auto prop = meta.GetProperty<const BASE_NS::string>("Name", META_NS::MetadataQuery::EXISTING);
if (prop) {
return META_NS::GetValue(prop);
}
return {};
}
void ResourceTemplateBase::PromoteToDefaults()
{
if (promoted_) {
return;
}
promoted_ = true;
PromoteMetadata(*this);
}
void ResourceTemplateBase::SetTemplateContext(bool isTemplate)
{
if (isTemplate && !promoted_) {
PromoteToDefaults();
}
templateContext_ = isTemplate;
}
bool ResourceTemplateBase::ValidateResourceType(const CORE_NS::IResource& res) const
{
return true;
}
bool ResourceTemplateBase::CopyFromTyped(const META_NS::IObject& source, bool onlySetValues)
{
return false;
}
void ResourceTemplateBase::CloneSubObjects(const META_NS::IMetadata& srcMeta, META_NS::IMetadata& cloneMeta) const
{}
bool ResourceTemplateBase::ApplyTo(META_NS::IObject& target) const
{
auto dst = interface_cast<META_NS::IMetadata>(&target);
if (!dst) {
return false;
}
CopyMetadata(*this, *dst, CopyMode::OVERRIDES_ONLY);
return true;
}
CORE_NS::IResource::Ptr ResourceTemplateBase::ResolveBaseResource(const CORE_NS::ResourceContextPtr& context) const
{
if (!baseResource_.IsValid()) {
return nullptr;
}
auto rm = GetResourceManagerFromContext(context);
if (!rm) {
return nullptr;
}
auto base = rm->GetResource({baseResource_, context});
if (!base) {
base = rm->GetResource(CORE_NS::ResourceIdContext{baseResource_});
}
return base;
}
void ResourceTemplateBase::ApplyBaseResource(
CORE_NS::IResource& res, const CORE_NS::IResource::Ptr& base, const ApplyBaseDefaultsFn& applyBaseDefaults) const
{
if (!base) {
CORE_LOG_W("Could not load base resource for %s", res.GetResourceId().ToString().c_str());
return;
}
auto baseMeta = interface_cast<META_NS::IMetadata>(base);
auto resObj = interface_cast<META_NS::IObject>(&res);
auto resMeta = interface_cast<META_NS::IMetadata>(&res);
if (!baseMeta || !resObj || !resMeta) {
return;
}
if (applyBaseDefaults) {
applyBaseDefaults(*resObj, *baseMeta);
} else {
DefaultApplyBaseDefaults(*resObj, *baseMeta);
}
if (auto baseTmpl = interface_pointer_cast<IResourceTemplate>(base)) {
baseTmpl->ApplyTo(*resObj);
}
if (auto derived = interface_cast<META_NS::IDerivedFromTemplate>(&res)) {
derived->SetTemplateId(baseResource_);
}
CopyIntoReadonlySubObjects(*baseMeta, *resMeta);
}
void ResourceTemplateBase::StampTemplateId(CORE_NS::IResource& res) const
{
if (auto id = interface_cast<META_NS::IDerivedFromTemplate>(&res)) {
id->SetTemplateId(GetResourceId());
}
}
void ResourceTemplateBase::MarkImportedFromTemplate(META_NS::IMetadata& resMeta) const
{
META_NS::SetObjectFlags(&resMeta, IMPORTED_FROM_TEMPLATE_BIT, true);
}
bool ResourceTemplateBase::CopyFrom(const META_NS::IObject& source, bool onlySetValues)
{
if (CopyFromTyped(source, onlySetValues)) {
return true;
}
auto srcMeta = interface_cast<META_NS::IMetadata>(&source);
if (!srcMeta) {
return false;
}
META_NS::IMetadata& dstMeta = *this;
CopyMetadata(*srcMeta, dstMeta, onlySetValues ? CopyMode::OVERRIDES_ONLY : CopyMode::WITH_DEFAULTS);
return true;
}
bool ResourceTemplateBase::ApplyOptions(CORE_NS::IResource& res, const CORE_NS::ResourceContextPtr& context) const
{
if (!ValidateResourceType(res)) {
return false;
}
auto resObj = interface_cast<META_NS::IObject>(&res);
if (!resObj) {
return false;
}
if (baseResource_.IsValid()) {
ApplyBaseResource(res, ResolveBaseResource(context));
}
StampTemplateId(res);
auto resMeta = interface_cast<META_NS::IMetadata>(&res);
if (templateContext_ && !baseResource_.IsValid() && resMeta) {
CopyMetadata(*this, *resMeta, CopyMode::WITH_DEFAULTS);
}
auto applied = ApplyTo(*resObj);
if (applied && templateContext_ && resMeta) {
if (baseResource_.IsValid()) {
CopyMetadata(*this, *resMeta, CopyMode::WITH_DEFAULTS);
}
MarkImportedFromTemplate(*resMeta);
}
return applied;
}
bool ResourceTemplateBase::UpdateOptions(const CORE_NS::IResource& res, const CORE_NS::ResourceContextPtr& context)
{
auto obj = interface_cast<META_NS::IObject>(&res);
if (!obj) {
return false;
}
return CopyFrom(static_cast<const META_NS::IObject&>(*obj));
}
bool ResourceTemplateBase::Merge(const CORE_NS::IResourceOptions& options)
{
auto otherMeta = interface_cast<const META_NS::IMetadata>(&options);
if (!otherMeta) {
return false;
}
PromoteToDefaults();
META_NS::IMetadata& thisMeta = *this;
CopyMetadata(*otherMeta, thisMeta, CopyMode::OVERRIDES_ONLY);
if (auto derived = interface_cast<const META_NS::IDerivedResourceOptions>(&options)) {
auto otherBase = derived->GetBaseResource();
if (otherBase.IsValid()) {
baseResource_ = BASE_NS::move(otherBase);
}
}
return true;
}
CORE_NS::IResourceOptions::Ptr ResourceTemplateBase::Clone() const
{
auto clone = META_NS::GetObjectRegistry().Create<CORE_NS::IResourceOptions>(GetTemplateClassId());
if (!clone) {
return nullptr;
}
auto cloneMeta = interface_cast<META_NS::IMetadata>(clone);
if (!cloneMeta) {
return nullptr;
}
const META_NS::IMetadata& srcMeta = *this;
for (auto&& srcProp : srcMeta.GetProperties()) {
auto name = srcProp->GetName();
auto md = srcMeta.GetMetadata(META_NS::MetadataType::PROPERTY, name);
if (md.readOnly) {
} else if (auto dstProp = cloneMeta->GetProperty(name)) {
CopyProperty(srcProp, *dstProp, CopyMode::WITH_DEFAULTS);
} else {
ClonePropertyWithDefaults(srcProp, *cloneMeta);
}
}
CloneSubObjects(srcMeta, *cloneMeta);
if (auto derived = interface_cast<META_NS::IDerivedResourceOptions>(clone)) {
derived->SetBaseResource(baseResource_);
}
if (auto pt = interface_cast<IPromotableTemplate>(clone)) {
pt->SetPromoted(promoted_);
}
if (auto tc = interface_cast<ITemplateOptions>(clone)) {
tc->SetTemplateContext(templateContext_);
}
return clone;
}
SCENE_END_NAMESPACE()