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 <core/intf_engine.h>
17 #include <render/intf_render_context.h>
18 #include <util/io_util.h>
19 #include <util/json.h>
20 #include <util/path_util.h>
21
22 #include <ecs_serializer/api.h>
23 #include <ecs_serializer/intf_ecs_asset_manager.h>
24
25 using namespace BASE_NS;
26 using namespace CORE_NS;
27 using namespace RENDER_NS;
28 using namespace CORE3D_NS;
29 using namespace UTIL_NS;
30
31 ECS_SERIALIZER_BEGIN_NAMESPACE()
32
33 class EcsAssetManager : public IEcsAssetManager {
34 public:
35 explicit EcsAssetManager(CORE3D_NS::IGraphicsContext& graphicsContext);
36 virtual ~EcsAssetManager();
37 // From IEcsAssetManager
38 ExtensionType GetExtensionType(BASE_NS::string_view ext) const override;
39
40 IEcsAssetLoader::Ptr CreateEcsAssetLoader(
41 IEntityCollection& ec, BASE_NS::string_view src, BASE_NS::string_view contextUri) override;
42
43 bool LoadAsset(IEntityCollection& ec, BASE_NS::string_view uri, BASE_NS::string_view contextUri) override;
44 bool SaveJsonEntityCollection(
45 const IEntityCollection& ec, BASE_NS::string_view uri, BASE_NS::string_view contextUri) const override;
46
47 IEntityCollection* LoadAssetToCache(CORE_NS::IEcs& ecs, BASE_NS::string_view cacheUri, BASE_NS::string_view uri,
48 BASE_NS::string_view contextUri, bool active, bool forceReload) override;
49
50 bool IsCachedCollection(BASE_NS::string_view uri, BASE_NS::string_view contextUri) const override;
51 IEntityCollection* CreateCachedCollection(
52 CORE_NS::IEcs& ecs, BASE_NS::string_view uri, BASE_NS::string_view contextUri) override;
53 IEntityCollection* GetCachedCollection(BASE_NS::string_view uri, BASE_NS::string_view contextUri) const override;
54 void RemoveFromCache(BASE_NS::string_view uri, BASE_NS::string_view contextUri) override;
55 void ClearCache() override;
56
57 void RefreshAsset(IEntityCollection& ec, bool active) override;
58
59 IEntityCollection* InstantiateCollection(
60 IEntityCollection& ec, BASE_NS::string_view uri, BASE_NS::string_view contextUri) override;
61
62 IEcsSerializer& GetEcsSerializer() override;
63 const IEcsSerializer& GetEcsSerializer() const override;
64 // From IEcsSerializer::IListener
65 IEntityCollection* GetExternalCollection(
66 CORE_NS::IEcs& ecs, BASE_NS::string_view uri, BASE_NS::string_view contextUri) override;
67
68 protected:
69 void Destroy() override;
70
71 private:
72 RENDER_NS::IRenderContext& renderContext_;
73 CORE3D_NS::IGraphicsContext& graphicsContext_;
74 IEcsSerializer::Ptr ecsSerializer_;
75
76 // Mapping from uri to a collection of entities. The entities live in the ECS but we need to keep track of some kind
77 // of internal ownership of each cached entity.
78 BASE_NS::unordered_map<BASE_NS::string, IEntityCollection::Ptr> cacheCollections_;
79 };
80
EcsAssetManager(IGraphicsContext & graphicsContext)81 EcsAssetManager::EcsAssetManager(IGraphicsContext& graphicsContext)
82 : renderContext_(graphicsContext.GetRenderContext()), graphicsContext_(graphicsContext),
83 ecsSerializer_(CreateEcsSerializer(renderContext_))
84 {
85 ecsSerializer_->SetDefaultSerializers();
86 ecsSerializer_->SetListener(this);
87 }
88
~EcsAssetManager()89 EcsAssetManager::~EcsAssetManager() {}
90
GetExtensionType(string_view ext) const91 EcsAssetManager::ExtensionType EcsAssetManager::GetExtensionType(string_view ext) const
92 {
93 // Ignore case.
94 // Better type recognition
95 if (ext == "collection") {
96 return ExtensionType::COLLECTION;
97 } else if (ext == "scene") {
98 return ExtensionType::SCENE;
99 } else if (ext == "prefab") {
100 return ExtensionType::PREFAB;
101 } else if (ext == "animation") {
102 return ExtensionType::ANIMATION;
103 } else if (ext == "material") {
104 return ExtensionType::MATERIAL;
105 } else if (ext == "gltf") {
106 return ExtensionType::GLTF;
107 } else if (ext == "glb") {
108 return ExtensionType::GLB;
109 } else if (ext == "png") {
110 return ExtensionType::PNG;
111 } else if (ext == "jpg" || ext == "jpeg") {
112 return ExtensionType::JPG;
113 } else if (ext == "ktx") {
114 return ExtensionType::KTX;
115 } else {
116 return ExtensionType::NOT_SUPPORTED;
117 }
118 }
119
CreateEcsAssetLoader(IEntityCollection & ec,string_view src,string_view contextUri)120 IEcsAssetLoader::Ptr EcsAssetManager::CreateEcsAssetLoader(
121 IEntityCollection& ec, string_view src, string_view contextUri)
122 {
123 return ::ECS_SERIALIZER_NS::CreateEcsAssetLoader(*this, graphicsContext_, ec, src, contextUri);
124 }
125
LoadAsset(IEntityCollection & ec,string_view uri,string_view contextUri)126 bool EcsAssetManager::LoadAsset(IEntityCollection& ec, string_view uri, string_view contextUri)
127 {
128 auto assetLoader = CreateEcsAssetLoader(ec, uri, contextUri);
129 if (assetLoader) {
130 // Use syncronous asset loading.
131 assetLoader->LoadAsset();
132 auto result = assetLoader->GetResult();
133 return result.success;
134 }
135 return false;
136 }
137
SaveJsonEntityCollection(const IEntityCollection & ec,string_view uri,string_view contextUri) const138 bool EcsAssetManager::SaveJsonEntityCollection(
139 const IEntityCollection& ec, string_view uri, string_view contextUri) const
140 {
141 const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
142 CORE_LOG_D("SaveJsonEntityCollection: '%s'", resolvedUri.c_str());
143
144 json::standalone_value jsonOut;
145 auto result = GetEcsSerializer().WriteEntityCollection(ec, jsonOut);
146 if (result) {
147 const string jsonString = to_formatted_string(jsonOut, 4);
148 auto& fileManager = renderContext_.GetEngine().GetFileManager();
149 if (IoUtil::SaveTextFile(fileManager, resolvedUri, { jsonString.data(), jsonString.size() })) {
150 return true;
151 }
152 }
153 return false;
154 }
155
LoadAssetToCache(IEcs & ecs,string_view cacheUri,string_view uri,string_view contextUri,bool active,bool forceReload)156 IEntityCollection* EcsAssetManager::LoadAssetToCache(
157 IEcs& ecs, string_view cacheUri, string_view uri, string_view contextUri, bool active, bool forceReload)
158 {
159 // Check if this collection was already loaded.
160 const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
161 const auto& cacheId = cacheUri;
162
163 if (!forceReload) {
164 auto it = cacheCollections_.find(cacheId);
165 if (it != cacheCollections_.cend()) {
166 return it->second.get();
167 }
168 }
169
170 // Not yet loaded (or forcing reload) -> load the entity collection.
171 // Need to first remove the possible cached collection because otherwise gltf importer will not reload the resources
172 // like meshes if they have the same names.
173 cacheCollections_.erase(cacheId);
174
175 auto ec = CreateEntityCollection(ecs, cacheUri, contextUri);
176 if (LoadAsset(*ec, resolvedUri, contextUri)) {
177 // Something was loaded. Set all loaded entities as inactive if requested.
178 if (!active) {
179 ec->SetActive(false);
180 }
181 // Add loaded collection to cache map.
182 return (cacheCollections_[cacheId] = move(ec)).get();
183 } else {
184 return nullptr;
185 }
186 }
187
IsCachedCollection(string_view uri,string_view contextUri) const188 bool EcsAssetManager::IsCachedCollection(string_view uri, string_view contextUri) const
189 {
190 const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
191 const auto& cacheId = resolvedUri;
192
193 return cacheCollections_.find(cacheId) != cacheCollections_.cend();
194 }
195
CreateCachedCollection(IEcs & ecs,BASE_NS::string_view uri,BASE_NS::string_view contextUri)196 IEntityCollection* EcsAssetManager::CreateCachedCollection(
197 IEcs& ecs, BASE_NS::string_view uri, BASE_NS::string_view contextUri)
198 {
199 auto ec = CreateEntityCollection(ecs, uri, contextUri);
200 ec->SetActive(false);
201
202 const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
203 const auto& cacheId = resolvedUri;
204
205 // Add the created collection to the cache map.
206 return (cacheCollections_[cacheId] = move(ec)).get();
207 }
208
GetCachedCollection(string_view uri,string_view contextUri) const209 IEntityCollection* EcsAssetManager::GetCachedCollection(string_view uri, string_view contextUri) const
210 {
211 const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
212 const auto& cacheId = resolvedUri;
213
214 auto collection = cacheCollections_.find(cacheId);
215 return collection != cacheCollections_.cend() ? collection->second.get() : nullptr;
216 }
217
RemoveFromCache(string_view uri,string_view contextUri)218 void EcsAssetManager::RemoveFromCache(string_view uri, string_view contextUri)
219 {
220 const auto resolvedUri = PathUtil::ResolveUri(contextUri, uri);
221 const auto& cacheId = resolvedUri;
222
223 cacheCollections_.erase(cacheId);
224 }
225
ClearCache()226 void EcsAssetManager::ClearCache()
227 {
228 // NOTE: not removing any active collections.
229 for (auto it = cacheCollections_.begin(); it != cacheCollections_.end();) {
230 if (!it->second->IsActive()) {
231 it = cacheCollections_.erase(it);
232 } else {
233 ++it;
234 }
235 }
236 }
237
RefreshAsset(IEntityCollection & ec,bool active)238 void EcsAssetManager::RefreshAsset(IEntityCollection& ec, bool active)
239 {
240 json::standalone_value json;
241 if (!GetEcsSerializer().WriteEntityCollection(ec, json)) {
242 return;
243 }
244
245 if (json) {
246 ec.Clear();
247 // Set the active state when the collection empty to not iterate all the contents.
248 ec.SetActive(active);
249 // NOTE: This does conversion from standalone json to read only json.
250 GetEcsSerializer().ReadEntityCollection(ec, json, ec.GetContextUri());
251 }
252 }
253
InstantiateCollection(IEntityCollection & ec,string_view uri,string_view contextUri)254 IEntityCollection* EcsAssetManager::InstantiateCollection(
255 IEntityCollection& ec, string_view uri, string_view contextUri)
256 {
257 #ifdef VERBOSE_LOGGING
258 CORE_LOG_D("InstantiateCollection: uri='%s' context='%s'", string(uri).c_str(), string(contextUri).c_str());
259 #endif
260 auto& ecs = ec.GetEcs();
261 auto externalCollection = LoadAssetToCache(ecs, uri, uri, contextUri, false, false);
262 if (externalCollection) {
263 auto& newCollection = ec.AddSubCollectionClone(*externalCollection, {});
264 newCollection.SetSrc(uri);
265 newCollection.MarkModified(false);
266 return &newCollection;
267 }
268
269 CORE_LOG_E("Loading collection failed: '%s'", string(uri).c_str());
270 return nullptr;
271 }
272
GetEcsSerializer()273 IEcsSerializer& EcsAssetManager::GetEcsSerializer()
274 {
275 return *ecsSerializer_;
276 }
277
GetEcsSerializer() const278 const IEcsSerializer& EcsAssetManager::GetEcsSerializer() const
279 {
280 return *ecsSerializer_;
281 }
282
GetExternalCollection(IEcs & ecs,string_view uri,string_view contextUri)283 IEntityCollection* EcsAssetManager::GetExternalCollection(IEcs& ecs, string_view uri, string_view contextUri)
284 {
285 // NOTE: Does not automatically try to load external dependencies.
286 return GetCachedCollection(uri, contextUri);
287 }
288
Destroy()289 void EcsAssetManager::Destroy()
290 {
291 delete this;
292 }
293
CreateEcsAssetManager(CORE3D_NS::IGraphicsContext & graphicsContext)294 IEcsAssetManager::Ptr CreateEcsAssetManager(CORE3D_NS::IGraphicsContext& graphicsContext)
295 {
296 return IEcsAssetManager::Ptr { new EcsAssetManager(graphicsContext) };
297 }
298
299 ECS_SERIALIZER_END_NAMESPACE()
300