* 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 "scene_manager.h"
#include <scene/ext/intf_ecs_context.h>
#include <scene/ext/intf_internal_scene.h>
#include <scene/ext/util.h>
#include <scene/interface/intf_application_context.h>
#include <scene/interface/intf_environment.h>
#include <scene/interface/intf_image.h>
#include <scene/interface/intf_material.h>
#include <scene/interface/intf_postprocess.h>
#include <scene/interface/intf_scene.h>
#include <scene/interface/intf_scene_manager.h>
#include <scene/interface/intf_shader.h>
#include <scene/interface/resource/types.h>
#include <scene/interface/serialization/intf_metadata_importer.h>
#include <scene/interface/serialization/intf_scene_file_loader.h>
#include <core/intf_engine.h>
#include <core/json/json.h>
#include <render/intf_render_context.h>
#include <meta/api/task_queue.h>
#include <meta/interface/builtin_objects.h>
#include <meta/interface/intf_object_hierarchy_observer.h>
#include <meta/interface/resource/intf_object_resource.h>
#include "asset/asset_object.h"
#include "perf/cpu_perf_scope.h"
#include "resource/util.h"
SCENE_BEGIN_NAMESPACE()
static constexpr auto DEFAULT_PROJECT_RESOURCE_GROUP_URI = "project://default_resources.res";
static constexpr auto PROJECT_JSON_URI = "project://project.json";
static constexpr auto JSON_IMPORTER_V1_VERSION = "JsonImporter v1";
struct ProjectInfo {
BASE_NS::string importVersion;
BASE_NS::vector<BASE_NS::string> resourceUris;
};
static bool HasDefaultResourceGroup(const CORE_NS::IResourceManager::Ptr& resources);
static ProjectInfo ReadProjectInfo(const CORE_NS::IResourceManager::Ptr& resources);
static BASE_NS::vector<BASE_NS::string> BuildSceneLoaderIndices(
const CORE_NS::IResourceManager::Ptr& resources, BASE_NS::vector<BASE_NS::string> projectUris);
static IScene::Ptr LoadJsonImporterScene(
const IRenderContext::Ptr& renderContext, BASE_NS::string_view path, BASE_NS::vector<BASE_NS::string> resourceUris);
bool SceneManager::UseDefaultContext()
{
auto app = GetDefaultApplicationContext();
if (!app) {
return true;
}
CORE_LOG_D("Using default context for scene manager");
context_ = app->GetRenderContext();
if (!context_) {
CORE_LOG_W("No default context set");
return false;
}
opts_ = app->GetDefaultSceneOptions();
return true;
}
bool SceneManager::Build(const META_NS::IMetadata::Ptr& d)
{
if (!Super::Build(d)) {
return false;
}
if (d) {
context_ = GetInterfaceBuildArg<IRenderContext>(d, "RenderContext");
opts_ = GetBuildArg<SceneOptions>(d, "Options");
}
if (!context_ && !UseDefaultContext()) {
return false;
}
if (auto importer = META_NS::GetObjectRegistry().Create<IMetadataImporter>(ClassId::MetadataImporter)) {
importer->RegisterResourceTypes(context_, opts_);
}
return true;
}
META_NS::IMetadata::Ptr SceneManager::CreateContext(SceneOptions opts) const
{
auto context = CreateRenderContextArg(context_);
if (context) {
if (auto op = META_NS::ConstructProperty<SceneOptions>("Options", opts)) {
context->AddProperty(op);
}
}
return context;
}
Future<IScene::Ptr> SceneManager::CreateScene()
{
return CreateScene(opts_);
}
Future<IScene::Ptr> SceneManager::CreateScene(SceneOptions opts)
{
return context_->AddTaskOrRunDirectly([context = CreateContext(BASE_NS::move(opts))] {
if (auto scene = META_NS::GetObjectRegistry().Create<IScene>(SCENE_NS::ClassId::Scene, context)) {
auto iScene = scene->GetInternalScene();
if (auto c = iScene->GetContext()) {
if (auto res = c->GetResources()) {
auto group = UniqueGroupName(res, "Scene", scene);
SetPrimaryGroupOnly(scene, group);
}
}
auto& ecs = iScene->GetEcsContext();
if (ecs.CreateUnnamedRootNode()) {
return scene;
}
CORE_LOG_E("Failed to create root node");
}
return SCENE_NS::IScene::Ptr{};
});
}
static IScene::Ptr Load(const IScene::Ptr& scene, BASE_NS::string_view uri, bool createResources,
const CORE_NS::ResourceId& rid, int64_t offset)
{
CORE_LOG_I("Loading scene: '%s'", BASE_NS::string(uri).c_str());
if (auto assets = META_NS::GetObjectRegistry().Create<IAssetObject>(ClassId::AssetObject)) {
if (assets->Load(scene, uri, createResources, rid, offset)) {
if (auto att = interface_cast<META_NS::IAttach>(scene)) {
att->Attach(assets);
}
return scene;
}
}
return {};
}
Future<IScene::Ptr> SceneManager::CreateScene(BASE_NS::string_view uri)
{
return CreateScene(uri, opts_);
}
Future<IScene::Ptr> SceneManager::CreateScene(BASE_NS::string_view uri, int64_t offset)
{
opts_.dataOffset = offset;
return CreateScene(uri, opts_);
}
Future<IScene::Ptr> SceneManager::CreateScene(BASE_NS::string_view uri, SceneOptions opts)
{
if (uri == "" || uri == "scene://empty") {
return CreateScene(BASE_NS::move(opts));
}
if (!context_) {
return {};
}
bool createRes = opts.createResources;
CORE_NS::ResourceId rid = opts.resourceId;
int64_t offset = opts.dataOffset;
return context_->AddTaskOrRunDirectly([path = BASE_NS::string(uri), renderContext = context_,
args = CreateContext(BASE_NS::move(opts)), createRes, rid, offset] {
IRenderContext::RenderContextScope sceneLoadScope(renderContext, IRenderContext::ScopeType::SceneLoad);
IScene::Ptr result;
const auto pathLowerCase = path.toLower();
if (pathLowerCase.ends_with(".gltf") || pathLowerCase.ends_with(".glb") || pathLowerCase.ends_with(".mp4")) {
result = META_NS::GetObjectRegistry().Create<IScene>(SCENE_NS::ClassId::Scene, args);
if (result) {
result = Load(result, path, createRes, rid, offset);
}
} else if (SetProjectPath(renderContext, path, ProjectPathAction::REGISTER)) {
auto info = ReadProjectInfo(renderContext->GetResources());
if (info.importVersion == JSON_IMPORTER_V1_VERSION) {
result = LoadJsonImporterScene(renderContext, path, BASE_NS::move(info.resourceUris));
} else {
result = LoadSceneWithIndex(renderContext, path, BASE_NS::move(info.resourceUris));
}
SetProjectPath(renderContext, path, ProjectPathAction::UNREGISTER);
}
return result;
});
}
static bool HasDefaultResourceGroup(const CORE_NS::IResourceManager::Ptr& resources)
{
if (!resources) {
return false;
}
for (auto&& group : resources->GetResourceGroups(nullptr)) {
if (group.empty()) {
return true;
}
}
return false;
}
void SceneManager::LoadDefaultResourcesIfNeeded(const CORE_NS::IResourceManager::Ptr& resources)
{
SCENE_CPU_PERF_SCOPE("LoadScene", "LoadDefaultResources");
if (!HasDefaultResourceGroup(resources)) {
resources->Import(DEFAULT_PROJECT_RESOURCE_GROUP_URI);
}
}
static ProjectInfo ReadProjectInfo(const CORE_NS::IResourceManager::Ptr& resources)
{
ProjectInfo info;
if (!resources) {
return info;
}
auto fileManager = resources->GetFileManager();
if (!fileManager) {
return info;
}
auto file = fileManager->OpenFile(PROJECT_JSON_URI);
if (!file) {
return info;
}
const uint64_t byteLength = file->GetLength();
BASE_NS::string raw(static_cast<size_t>(byteLength), BASE_NS::string::value_type());
if (file->Read(raw.data(), byteLength) != byteLength) {
CORE_LOG_W("Failed to read project.json");
return info;
}
const auto json = CORE_NS::json::parse(raw.c_str());
if (!json) {
CORE_LOG_W("Failed to parse project.json");
return info;
}
if (const auto* importVersion = json.find("importVersion"); importVersion && importVersion->is_string()) {
info.importVersion = CORE_NS::json::unescape(importVersion->string_);
CORE_LOG_D("project.json importVersion: '%s'", info.importVersion.c_str());
}
const auto* resourcesArray = json.find("resources");
if (!resourcesArray || !resourcesArray->is_array()) {
return info;
}
info.resourceUris.reserve(resourcesArray->array_.size());
for (const auto& entry : resourcesArray->array_) {
if (entry.is_string()) {
info.resourceUris.push_back(BASE_NS::string{entry.string_});
}
}
return info;
}
static BASE_NS::vector<BASE_NS::string> BuildSceneLoaderIndices(
const CORE_NS::IResourceManager::Ptr& resources, BASE_NS::vector<BASE_NS::string> projectUris)
{
BASE_NS::vector<BASE_NS::string> indices;
if (HasDefaultResourceGroup(resources)) {
return indices;
}
indices = BASE_NS::move(projectUris);
if (indices.empty()) {
indices.push_back(DEFAULT_PROJECT_RESOURCE_GROUP_URI);
}
return indices;
}
static IScene::Ptr LoadJsonImporterScene(
const IRenderContext::Ptr& renderContext, BASE_NS::string_view path, BASE_NS::vector<BASE_NS::string> resourceUris)
{
auto loader = META_NS::GetObjectRegistry().Create<ISceneFileLoader>(ClassId::SceneFileLoader);
if (!loader) {
return {};
}
auto indices = BuildSceneLoaderIndices(renderContext->GetResources(), BASE_NS::move(resourceUris));
return loader->LoadScene(renderContext, path, indices);
}
bool SceneManager::LoadProjectResources(
const CORE_NS::IResourceManager::Ptr& resources, const BASE_NS::vector<BASE_NS::string>& resourceUris)
{
SCENE_CPU_PERF_SCOPE("LoadScene", "LoadProjectResources");
if (resourceUris.empty()) {
return false;
}
for (const auto& uri : resourceUris) {
CORE_LOG_D("Importing project resource: '%s'", uri.c_str());
resources->Import(uri);
}
return true;
}
IScene::Ptr SceneManager::LoadSceneWithIndex(
const IRenderContext::Ptr& context, BASE_NS::string_view uri, BASE_NS::vector<BASE_NS::string> resourceUris)
{
SCENE_CPU_PERF_SCOPE("LoadScene", uri);
if (!context) {
return {};
}
auto resources = context->GetResources();
if (!resources) {
return {};
}
if (!HasDefaultResourceGroup(resources)) {
if (!LoadProjectResources(resources, resourceUris)) {
LoadDefaultResourcesIfNeeded(resources);
}
}
CORE_NS::ResourceIdContext rid{BASE_NS::string(uri)};
bool missingSceneRid = !resources->GetResourceInfo(rid).id.IsValid();
if (missingSceneRid) {
resources->AddResource(rid, ClassId::SceneResource.Id().ToUid(), uri);
}
auto result = interface_pointer_cast<IScene>(resources->GetResource(rid));
if (result) {
SCENE_CPU_PERF_SCOPE("LoadScene", "PostLoadTrim");
auto root = result->GetRootNode().GetResult();
if (auto i = result->GetInternalScene()) {
i->SyncProperties();
i->ReleaseNode(BASE_NS::move(root), true);
auto groups = i->GetResourceGroups();
for (auto&& g : groups.GetAllHandles()) {
auto name = g->GetGroup();
CORE_LOG_D("Purging resource group '%s'", name.c_str());
resources->PurgeGroup(name, result);
}
}
}
if (missingSceneRid) {
resources->RemoveResource(rid);
}
return result;
}
bool SceneManager::SetProjectPath(
const IRenderContext::Ptr& renderContext, BASE_NS::string_view uri, ProjectPathAction action)
{
if (uri.starts_with("project:")) {
return true;
}
if (renderContext) {
if (const auto renderer = renderContext->GetRenderer()) {
auto& fileManager = renderer->GetEngine().GetFileManager();
const auto pathToProject = GuessProjectPath(uri);
if (action == ProjectPathAction::REGISTER) {
return fileManager.RegisterPath("project", pathToProject, true);
} else {
fileManager.UnregisterPath("project", pathToProject);
}
return true;
}
}
CORE_LOG_E("Unable to access file manager: render context missing");
return false;
}
BASE_NS::string SceneManager::GuessIndexFilePath(BASE_NS::string_view uri)
{
auto pos = uri.find_last_of('.');
return uri.substr(0, pos) + ".res";
}
BASE_NS::string SceneManager::GuessProjectPath(BASE_NS::string_view uri)
{
const auto secondToLastSlashPos = uri.find_last_of('/', uri.find_last_of('/') - 1);
return BASE_NS::string{uri.substr(0, secondToLastSlashPos)};
}
SCENE_END_NAMESPACE()