1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "asset_manager.h"
17 
18 #include <core/intf_engine.h>
19 #include <core/io/intf_file_manager.h>
20 #include <core/log.h>
21 #include <render/intf_render_context.h>
22 
23 #include "asset_loader.h"
24 #include "entity_collection.h"
25 #include "json.h"
26 
27 using namespace BASE_NS;
28 using namespace CORE_NS;
29 using namespace RENDER_NS;
30 using namespace CORE3D_NS;
31 
SCENE_BEGIN_NAMESPACE()32 SCENE_BEGIN_NAMESPACE()
33 
34 AssetManager::AssetManager(IRenderContext& renderContext, IGraphicsContext& graphicsContext)
35     : renderContext_(renderContext), graphicsContext_(graphicsContext), ecsSerializer_(renderContext_)
36 {
37     ecsSerializer_.SetDefaultSerializers();
38     ecsSerializer_.SetListener(this);
39 }
40 
~AssetManager()41 AssetManager::~AssetManager() {}
42 
GetExtensionType(string_view ext) const43 AssetManager::ExtensionType AssetManager::GetExtensionType(string_view ext) const
44 {
45     // Ignore case.
46     // Better type recognition
47     if (ext == "collection") {
48         return ExtensionType::COLLECTION;
49     } else if (ext == "scene") {
50         return ExtensionType::SCENE;
51     } else if (ext == "animation") {
52         return ExtensionType::ANIMATION;
53     } else if (ext == "material") {
54         return ExtensionType::MATERIAL;
55     } else if (ext == "gltf") {
56         return ExtensionType::GLTF;
57     } else if (ext == "glb") {
58         return ExtensionType::GLB;
59     } else if (ext == "png") {
60         return ExtensionType::PNG;
61     } else if (ext == "jpg" || ext == "jpeg") {
62         return ExtensionType::JPG;
63     } else if (ext == "ktx") {
64         return ExtensionType::KTX;
65     } else {
66         return ExtensionType::NOT_SUPPORTED;
67     }
68 }
69 
CreateAssetLoader(IEntityCollection & ec,string_view src,string_view contextUri)70 IAssetLoader::Ptr AssetManager::CreateAssetLoader(IEntityCollection& ec, string_view src, string_view contextUri)
71 {
72     return SCENE_NS::CreateAssetLoader(*this, renderContext_, graphicsContext_, ec, src, contextUri);
73 }
74 
LoadAsset(IEntityCollection & ec,string_view uri,string_view contextUri)75 bool AssetManager::LoadAsset(IEntityCollection& ec, string_view uri, string_view contextUri)
76 {
77     auto assetLoader = CreateAssetLoader(ec, uri, contextUri);
78     if (assetLoader) {
79         //  Use syncronous asset loading.
80         assetLoader->LoadAsset();
81         auto result = assetLoader->GetResult();
82         return result.success;
83     }
84     return false;
85 }
86 
SaveTextFile(string_view fileUri,string_view fileContents,CORE_NS::IFileManager & fileManager)87 bool SaveTextFile(string_view fileUri, string_view fileContents, CORE_NS::IFileManager& fileManager)
88 {
89     // TODO: the safest way to save files would be to save to a temp file and rename.
90     SCENE_PLUGIN_VERBOSE_LOG("Save file: '%s'", string(fileUri).c_str());
91     auto file = fileManager.CreateFile(fileUri);
92     if (file) {
93         file->Write(fileContents.data(), fileContents.length());
94         file->Close();
95         return true;
96     }
97 
98     return false;
99 }
100 
SaveJsonEntityCollection(const IEntityCollection & ec,string_view uri,string_view contextUri) const101 bool AssetManager::SaveJsonEntityCollection(const IEntityCollection& ec, string_view uri, string_view contextUri) const
102 {
103     const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
104     SCENE_PLUGIN_VERBOSE_LOG("SaveJsonEntityCollection: '%s'", resolvedUri.c_str());
105 
106     json::standalone_value jsonOut;
107     auto result = ecsSerializer_.WriteEntityCollection(ec, jsonOut);
108     if (result) {
109         const string jsonString = CORE_NS::to_formatted_string(jsonOut, 4);
110         if (SaveTextFile(
111                 resolvedUri, { jsonString.data(), jsonString.size() }, renderContext_.GetEngine().GetFileManager())) {
112             return true;
113         }
114     }
115     return false;
116 }
117 
LoadAssetToCache(IEcs & ecs,string_view cacheUri,string_view uri,string_view contextUri,bool active,bool forceReload)118 IEntityCollection* AssetManager::LoadAssetToCache(
119     IEcs& ecs, string_view cacheUri, string_view uri, string_view contextUri, bool active, bool forceReload)
120 {
121     // Check if this collection was already loaded.
122     const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
123     const auto& cacheId = cacheUri;
124 
125     if (!forceReload) {
126         auto it = cacheCollections_.find(cacheId);
127         if (it != cacheCollections_.cend()) {
128             return it->second.get();
129         }
130     }
131 
132     // Need to first remove the possible cached collection because otherwise gltf importer will not reload the resources
133     // like meshes if they have the same names.
134     cacheCollections_.erase(cacheId);
135 
136     // Load the entity collection.
137 
138     auto ec = EntityCollection::Ptr { new EntityCollection(ecs, uri, contextUri) };
139     if (LoadAsset(*ec, uri, contextUri)) {
140         // Something was loaded. Set all loaded entities as inactive if requested.
141         if (!active) {
142             ec->SetActive(false);
143         }
144         // Add loaded collection to cache map.
145         return (cacheCollections_[cacheId] = move(ec)).get();
146     } else {
147         return nullptr;
148     }
149 }
150 
IsCachedCollection(string_view uri,string_view contextUri) const151 bool AssetManager::IsCachedCollection(string_view uri, string_view contextUri) const
152 {
153     const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
154     const auto& cacheId = resolvedUri;
155 
156     return cacheCollections_.find(cacheId) != cacheCollections_.cend();
157 }
158 
CreateCachedCollection(IEcs & ecs,BASE_NS::string_view uri,BASE_NS::string_view contextUri)159 IEntityCollection* AssetManager::CreateCachedCollection(
160     IEcs& ecs, BASE_NS::string_view uri, BASE_NS::string_view contextUri)
161 {
162     auto ec = EntityCollection::Ptr { new EntityCollection(ecs, uri, contextUri) };
163     ec->SetActive(false);
164 
165     const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
166     const auto& cacheId = resolvedUri;
167 
168     // Add the created collection to the cache map.
169     return (cacheCollections_[cacheId] = move(ec)).get();
170 }
171 
GetCachedCollection(string_view uri,string_view contextUri) const172 IEntityCollection* AssetManager::GetCachedCollection(string_view uri, string_view contextUri) const
173 {
174     const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
175     const auto& cacheId = resolvedUri;
176 
177     auto collection = cacheCollections_.find(cacheId);
178     return collection != cacheCollections_.cend() ? collection->second.get() : nullptr;
179 }
180 
RemoveFromCache(string_view uri,string_view contextUri)181 void AssetManager::RemoveFromCache(string_view uri, string_view contextUri)
182 {
183     const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
184     const auto& cacheId = resolvedUri;
185 
186     cacheCollections_.erase(cacheId);
187 }
188 
ClearCache()189 void AssetManager::ClearCache()
190 {
191     // NOTE: not removing any active collections.
192     for (auto it = cacheCollections_.begin(); it != cacheCollections_.end();) {
193         if (!it->second->IsActive()) {
194             it = cacheCollections_.erase(it);
195         } else {
196             ++it;
197         }
198     }
199 }
200 
RefreshAsset(IEntityCollection & ec,bool active)201 void AssetManager::RefreshAsset(IEntityCollection& ec, bool active)
202 {
203     json::standalone_value json;
204     if (!ecsSerializer_.WriteEntityCollection(ec, json)) {
205         return;
206     }
207 
208     if (json) {
209         ec.Clear();
210         // Set the active state when the collection empty to not iterate all the contents.
211         ec.SetActive(active);
212         // NOTE: This does conversion from standalone json to read only json.
213         ecsSerializer_.ReadEntityCollection(ec, json, ec.GetContextUri());
214     }
215 }
216 
InstantiateCollection(IEntityCollection & ec,string_view uri,string_view contextUri)217 IEntityCollection* AssetManager::InstantiateCollection(IEntityCollection& ec, string_view uri, string_view contextUri)
218 {
219 #ifdef VERBOSE_LOGGING
220     SCENE_PLUGIN_VERBOSE_LOG(
221         "InstantiateCollection: uri='%s' context='%s'", string(uri).c_str(), string(contextUri).c_str());
222 #endif
223     auto& ecs = ec.GetEcs();
224     auto externalCollection = LoadAssetToCache(ecs, uri, uri, contextUri, false, false);
225     if (externalCollection) {
226         auto& newCollection = ec.AddSubCollectionClone(*externalCollection, {});
227         newCollection.SetSrc(uri);
228         newCollection.MarkModified(false);
229         return &newCollection;
230     }
231 
232     CORE_LOG_E("Loading collection failed: '%s'", string(uri).c_str());
233     return nullptr;
234 }
235 
GetEcsSerializer()236 IEcsSerializer& AssetManager::GetEcsSerializer()
237 {
238     return ecsSerializer_;
239 }
240 
GetExternalCollection(IEcs & ecs,string_view uri,string_view contextUri)241 IEntityCollection* AssetManager::GetExternalCollection(IEcs& ecs, string_view uri, string_view contextUri)
242 {
243     // NOTE: Does not automatically try to load external dependencies.
244     return GetCachedCollection(uri, contextUri);
245 }
246 
Destroy()247 void AssetManager::Destroy()
248 {
249     delete this;
250 }
251 
252 SCENE_END_NAMESPACE()
253