• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <3d/ecs/components/animation_component.h>
17 #include <3d/ecs/components/animation_track_component.h>
18 #include <3d/ecs/components/name_component.h>
19 #include <3d/ecs/components/node_component.h>
20 #include <3d/ecs/components/render_handle_component.h>
21 #include <3d/ecs/components/uri_component.h>
22 #include <3d/ecs/systems/intf_animation_system.h>
23 #include <3d/ecs/systems/intf_node_system.h>
24 #include <3d/gltf/gltf.h>
25 #include <3d/intf_graphics_context.h>
26 #include <base/containers/fixed_string.h>
27 #include <base/containers/vector.h>
28 #include <core/image/intf_image_loader_manager.h>
29 #include <core/intf_engine.h>
30 #include <ecs_serializer/ecs_animation_util.h>
31 #include <ecs_serializer/intf_ecs_asset_manager.h>
32 #include <render/device/intf_gpu_resource_manager.h>
33 #include <render/intf_render_context.h>
34 #include <util/io_util.h>
35 #include <util/path_util.h>
36 
37 #include "asset_migration.h"
38 #include <ecs_serializer/intf_ecs_asset_loader.h>
39 
40 using namespace BASE_NS;
41 using namespace CORE_NS;
42 using namespace RENDER_NS;
43 using namespace CORE3D_NS;
44 using namespace UTIL_NS;
45 
46 ECS_SERIALIZER_BEGIN_NAMESPACE()
47 
48 namespace {
49 // Add a node and all of its children recursively to a collection
AddNodeToCollectionRecursive(IEntityCollection & ec,ISceneNode & node,string_view path)50 void AddNodeToCollectionRecursive(IEntityCollection& ec, ISceneNode& node, string_view path)
51 {
52     const auto entity = node.GetEntity();
53     const auto currentPath = path + node.GetName();
54 
55     EntityReference ref = ec.GetEcs().GetEntityManager().GetReferenceCounted(entity);
56     ec.AddEntity(ref);
57     ec.SetId(currentPath, ref);
58 
59     const auto childBasePath = currentPath + "/";
60     for (auto* child : node.GetChildren()) {
61         AddNodeToCollectionRecursive(ec, *child, childBasePath);
62     }
63 }
64 } // namespace
65 
66 // A class that executes asset load operation over multiple frames.
67 class EcsAssetLoader final : public IEcsAssetLoader,
68                              private IEcsAssetLoader::IListener,
69                              private IGLTF2Importer::Listener {
70 public:
EcsAssetLoader(IEcsAssetManager & assetManager,IGraphicsContext & graphicsContext,IEntityCollection & ec,string_view src,string_view contextUri)71     EcsAssetLoader(IEcsAssetManager& assetManager, IGraphicsContext& graphicsContext, IEntityCollection& ec,
72         string_view src, string_view contextUri)
73         : assetManager_(assetManager), renderContext_(graphicsContext.GetRenderContext()),
74           graphicsContext_(graphicsContext), ecs_(ec.GetEcs()), ec_(ec), src_(src), contextUri_(contextUri)
75     {}
76 
~EcsAssetLoader()77     ~EcsAssetLoader() override
78     {
79         Cancel();
80     }
81 
GetEntityCollection() const82     IEntityCollection& GetEntityCollection() const override
83     {
84         return ec_;
85     }
86 
GetSrc() const87     string GetSrc() const override
88     {
89         return src_;
90     }
91 
GetContextUri() const92     string GetContextUri() const override
93     {
94         return contextUri_;
95     }
96 
GetUri() const97     string GetUri() const override
98     {
99         return PathUtil::ResolveUri(contextUri_, src_, true);
100     }
101 
AddListener(IEcsAssetLoader::IListener & listener)102     void AddListener(IEcsAssetLoader::IListener& listener) override
103     {
104         CORE_ASSERT(&listener);
105         listeners_.emplace_back(&listener);
106     }
107 
RemoveListener(IEcsAssetLoader::IListener & listener)108     void RemoveListener(IEcsAssetLoader::IListener& listener) override
109     {
110         CORE_ASSERT(&listener);
111         for (size_t i = 0; i < listeners_.size(); ++i) {
112             if (&listener == listeners_[i]) {
113                 listeners_.erase(listeners_.begin() + static_cast<int64_t>(i));
114                 return;
115             }
116         }
117 
118         // trying to remove a non-existent listener.
119         CORE_ASSERT(true);
120     }
121 
LoadAsset()122     void LoadAsset() override
123     {
124         async_ = false;
125         StartLoading();
126     }
127 
LoadAssetAsync()128     void LoadAssetAsync() override
129     {
130         async_ = true;
131         StartLoading();
132     }
133 
GetNextDependency() const134     IEcsAssetLoader* GetNextDependency() const
135     {
136         if (dependencies_.empty()) {
137             return nullptr;
138         }
139         for (const auto& dep : dependencies_) {
140             if (dep && !dep->IsCompleted()) {
141                 return dep.get();
142             }
143         }
144         return nullptr;
145     }
146 
147     // From IEcsAssetLoader::Listener
OnLoadFinished(const IEcsAssetLoader & loader)148     void OnLoadFinished(const IEcsAssetLoader& loader) override
149     {
150         // This will be called when a dependency has finished loading.
151         // Hide cached data.
152         loader.GetEntityCollection().SetActive(false);
153 
154         auto* dep = GetNextDependency();
155         if (dep) {
156             // Load next dependency.
157             ContinueLoading();
158         } else {
159             // There are no more dependencies. Try loading the main asset again but now with the dependencies loaded.
160             StartLoading();
161         }
162     }
163 
164     // From CORE_NS::IGLTF2Importer::Listener
OnImportStarted()165     void OnImportStarted() override {}
166 
OnImportFinished()167     void OnImportFinished() override
168     {
169         if (cancelled_) {
170             return;
171         }
172 
173         GltfImportFinished();
174         if (async_) {
175             ContinueLoading();
176         }
177     }
178 
OnImportProgressed(size_t taskIndex,size_t taskCount)179     void OnImportProgressed(size_t taskIndex, size_t taskCount) override
180     {
181         CORE_UNUSED(taskIndex);
182         CORE_UNUSED(taskCount);
183     }
184 
Execute(uint32_t timeBudget)185     bool Execute(uint32_t timeBudget) override
186     {
187         if (!async_) {
188             return true;
189         }
190         if (done_) {
191             return true;
192         }
193 
194         // NOTE: Currently actually only one import will be active at a time so we don't need to worry about the time
195         // budget.
196         bool done = true;
197 
198         for (auto& dep : dependencies_) {
199             if (dep) {
200                 done &= dep->Execute(timeBudget);
201             }
202         }
203         if (importer_) {
204             done &= importer_->Execute(timeBudget);
205         }
206 
207         if (done) {
208             for (auto* listener : listeners_) {
209                 listener->OnLoadFinished(*this);
210             }
211         }
212 
213         return done;
214     }
215 
Cancel()216     void Cancel() override
217     {
218         cancelled_ = true;
219         for (auto& dep : dependencies_) {
220             if (dep) {
221                 dep->Cancel();
222             }
223         }
224 
225         if (importer_) {
226             importer_->Cancel();
227             importer_.reset();
228         }
229     }
230 
IsCompleted() const231     bool IsCompleted() const override
232     {
233         return done_;
234     }
235 
GetResult() const236     Result GetResult() const override
237     {
238         return result_;
239     }
240 
Destroy()241     void Destroy() override
242     {
243         delete this;
244     }
245 
246 private:
StartLoading()247     void StartLoading()
248     {
249         if (src_.empty()) {
250             result_ = { false, "Ivalid uri" };
251             done_ = true;
252             return;
253         }
254 
255         const auto resolvedUri = GetUri();
256 
257         const auto resolvedFile = PathUtil::ResolveUri(contextUri_, src_, false);
258         const auto ext = PathUtil::GetExtension(resolvedFile);
259         const auto type = assetManager_.GetExtensionType(ext);
260         // Separate different loaders and make it possible to register more.
261         switch (type) {
262             case IEcsAssetManager::ExtensionType::COLLECTION: {
263                 ec_.SetType("entitycollection");
264                 auto result = LoadJsonEntityCollection();
265                 if (result.compatibilityInfo.versionMajor == 0 && result.compatibilityInfo.versionMinor == 0) {
266                     MigrateAnimation(ec_);
267                 }
268                 break;
269             }
270 
271             case IEcsAssetManager::ExtensionType::SCENE: {
272                 ec_.SetType("scene");
273                 auto result = LoadJsonEntityCollection();
274                 if (result.compatibilityInfo.versionMajor == 0 && result.compatibilityInfo.versionMinor == 0) {
275                     MigrateAnimation(ec_);
276                 }
277                 break;
278             }
279 
280             case IEcsAssetManager::ExtensionType::PREFAB: {
281                 ec_.SetType("prefab");
282                 auto result = LoadJsonEntityCollection();
283                 if (result.compatibilityInfo.versionMajor == 0 && result.compatibilityInfo.versionMinor == 0) {
284                     MigrateAnimation(ec_);
285                 }
286                 break;
287             }
288 
289             case IEcsAssetManager::ExtensionType::ANIMATION: {
290                 ec_.SetType("animation");
291                 auto result = LoadJsonEntityCollection();
292                 if (result.compatibilityInfo.versionMajor == 0 && result.compatibilityInfo.versionMinor == 0) {
293                     MigrateAnimation(ec_);
294                 }
295                 break;
296             }
297 
298             case IEcsAssetManager::ExtensionType::MATERIAL: {
299                 ec_.SetType("material");
300                 LoadJsonEntityCollection();
301                 break;
302             }
303 
304             case IEcsAssetManager::ExtensionType::GLTF:
305             case IEcsAssetManager::ExtensionType::GLB: {
306                 ec_.SetType("gltf");
307                 LoadGltfEntityCollection();
308                 break;
309             }
310 
311             case IEcsAssetManager::ExtensionType::JPG:
312             case IEcsAssetManager::ExtensionType::PNG:
313             case IEcsAssetManager::ExtensionType::KTX: {
314                 ec_.SetType("image");
315                 LoadImageEntityCollection();
316                 break;
317             }
318 
319             case IEcsAssetManager::ExtensionType::NOT_SUPPORTED:
320             default: {
321                 CORE_LOG_E("Unsupported asset format: '%s'", src_.c_str());
322                 result_ = { false, "Unsupported asset format" };
323                 done_ = true;
324                 break;
325             }
326         }
327 
328         ContinueLoading();
329     }
330 
ContinueLoading()331     void ContinueLoading()
332     {
333         if (done_) {
334             ec_.MarkModified(false, true);
335 
336             if (!async_) {
337                 for (auto* listener : listeners_) {
338                     listener->OnLoadFinished(*this);
339                 }
340             }
341             return;
342         }
343 
344         auto* dep = GetNextDependency();
345         if (dep) {
346             if (async_) {
347                 dep->LoadAssetAsync();
348             } else {
349                 dep->LoadAsset();
350             }
351             return;
352         }
353     }
354 
CreateDummyEntity()355     void CreateDummyEntity()
356     {
357         // Create a dummy root entity as a placeholder for a missing asset.
358         auto dummyEntity = ecs_.GetEntityManager().CreateReferenceCounted();
359         // Note: adding a node component for now so it will be displayed in the hierarchy pane.
360         // This is wrong as the failed asset might not be a 3D node in reality. this could be removed after combining
361         // the Entity pane functionlity to the hierarchy pane and when there is better handling for missing references.
362         auto* nodeComponentManager = GetManager<INodeComponentManager>(ecs_);
363         CORE_ASSERT(nodeComponentManager);
364         if (nodeComponentManager) {
365             nodeComponentManager->Create(dummyEntity);
366         }
367         if (!GetUri().empty()) {
368             auto* nameComponentManager = GetManager<INameComponentManager>(ecs_);
369             CORE_ASSERT(nameComponentManager);
370             if (nameComponentManager) {
371                 nameComponentManager->Create(dummyEntity);
372                 nameComponentManager->Write(dummyEntity)->name = GetUri();
373             }
374         }
375         ec_.AddEntity(dummyEntity);
376         ec_.SetId("/", dummyEntity);
377     }
378 
LoadJsonEntityCollection()379     IoUtil::SerializationResult LoadJsonEntityCollection()
380     {
381         const auto resolvedUri = GetUri();
382         const auto resolvedFile = PathUtil::ResolveUri(contextUri_, src_, false);
383 
384         string textIn;
385         auto& fileManager = renderContext_.GetEngine().GetFileManager();
386         if (IoUtil::LoadTextFile(fileManager, resolvedFile, textIn)) {
387             // Check file version here.
388             auto json = json::parse(textIn.data());
389             if (!json) {
390                 CORE_LOG_E("Parsing json failed: '%s':\n%s", resolvedUri.c_str(), textIn.c_str());
391             } else {
392                 if (!dependencies_.empty()) {
393                     // There were dependencies loaded earlier and now we want to load the actual asset.
394                     // No dependencies to load. Just load the entity collection itself and this loading is done.
395                     auto result = assetManager_.GetEcsSerializer().ReadEntityCollection(ec_, json, resolvedUri);
396                     done_ = true;
397                     return result;
398                 }
399 
400                 vector<IEcsSerializer::ExternalCollection> dependencies;
401                 assetManager_.GetEcsSerializer().GatherExternalCollections(json, resolvedUri, dependencies);
402 
403                 for (const auto& dep : dependencies) {
404                     if (!assetManager_.IsCachedCollection(dep.src, dep.contextUri)) {
405                         auto* cacheEc = assetManager_.CreateCachedCollection(ec_.GetEcs(), dep.src, dep.contextUri);
406                         dependencies_.emplace_back(
407                             assetManager_.CreateEcsAssetLoader(*cacheEc, dep.src, dep.contextUri));
408                         dependencies_.back()->AddListener(*this);
409                     }
410                 }
411 
412                 if (GetNextDependency() == nullptr) {
413                     // No dependencies to load. Just load the entity collection itself and this loading is done.
414                     auto result = assetManager_.GetEcsSerializer().ReadEntityCollection(ec_, json, resolvedUri);
415                     done_ = true;
416                     return result;
417                 }
418 
419                 // There are dependencies that need to be parsed in a next step.
420                 return {};
421             }
422         }
423 
424         CreateDummyEntity();
425         result_ = { false, "collection loading failed." };
426         done_ = true;
427 
428         IoUtil::SerializationResult serializationResult;
429         serializationResult.status = IoUtil::Status::ERROR_GENERAL;
430         serializationResult.error = result_.error;
431         return serializationResult;
432     }
433 
LoadGltfEntityCollection()434     void LoadGltfEntityCollection()
435     {
436         const auto resolvedFile = PathUtil::ResolveUri(contextUri_, src_, false);
437 
438         auto& gltf = graphicsContext_.GetGltf();
439 
440         loadResult_ = gltf.LoadGLTF(resolvedFile);
441         if (!loadResult_.success) {
442             CORE_LOG_E("Loaded '%s' with errors:\n%s", resolvedFile.c_str(), loadResult_.error.c_str());
443         }
444         if (!loadResult_.data) {
445             CreateDummyEntity();
446             result_ = { false, "glTF load failed." };
447             done_ = true;
448             return;
449         }
450 
451         importer_ = gltf.CreateGLTF2Importer(ec_.GetEcs());
452 
453         if (async_) {
454             importer_->ImportGLTFAsync(*loadResult_.data, CORE_GLTF_IMPORT_RESOURCE_FLAG_BITS_ALL, this);
455         } else {
456             importer_->ImportGLTF(*loadResult_.data, CORE_GLTF_IMPORT_RESOURCE_FLAG_BITS_ALL);
457             OnImportFinished();
458         }
459     }
460 
ImportSceneFromGltf(EntityReference root)461     Entity ImportSceneFromGltf(EntityReference root)
462     {
463         auto& gltf = graphicsContext_.GetGltf();
464 
465         // Import the default scene, or first scene if there is no default scene set.
466         size_t sceneIndex = loadResult_.data->GetDefaultSceneIndex();
467         if (sceneIndex == CORE_GLTF_INVALID_INDEX && loadResult_.data->GetSceneCount() > 0) {
468             // Use first scene.
469             sceneIndex = 0;
470         }
471 
472         const CORE3D_NS::GLTFResourceData& resourceData = importer_->GetResult().data;
473         Entity importedSceneEntity {};
474         if (sceneIndex != CORE_GLTF_INVALID_INDEX) {
475             GltfSceneImportFlags importFlags = CORE_GLTF_IMPORT_COMPONENT_FLAG_BITS_ALL;
476             importedSceneEntity =
477                 gltf.ImportGltfScene(sceneIndex, *loadResult_.data, resourceData, ecs_, root, importFlags);
478         }
479         if (!EntityUtil::IsValid(importedSceneEntity)) {
480             return {};
481         }
482 
483         // Link animation tracks to targets
484         if (!resourceData.animations.empty()) {
485             INodeSystem* nodeSystem = GetSystem<INodeSystem>(ecs_);
486             if (auto animationRootNode = nodeSystem->GetNode(importedSceneEntity); animationRootNode) {
487                 for (const auto& animationEntity : resourceData.animations) {
488                     ECS_SERIALIZER_NS::UpdateAnimationTrackTargets(
489                         ecs_, animationEntity, animationRootNode->GetEntity());
490                 }
491             }
492         }
493 
494         return importedSceneEntity;
495     }
496 
GltfImportFinished()497     void GltfImportFinished()
498     {
499         auto* nodeSystem = GetSystem<INodeSystem>(ecs_);
500         CORE_ASSERT(nodeSystem);
501         auto* nodeComponentManager = GetManager<INodeComponentManager>(ecs_);
502         CORE_ASSERT(nodeComponentManager);
503         if (!nodeSystem || !nodeComponentManager) {
504             result_ = { false, {} };
505             done_ = true;
506             return;
507         }
508 
509         const auto params = PathUtil::GetUriParameters(src_);
510         const auto rootParam = params.find("root");
511         const string entityPath = (rootParam != params.cend()) ? rootParam->second : "";
512 
513         if (importer_) {
514             const auto& gltfImportResult = importer_->GetResult();
515             if (!gltfImportResult.success) {
516                 CORE_LOG_E("Importing of '%s' failed: %s", GetUri().c_str(), gltfImportResult.error.c_str());
517                 CreateDummyEntity();
518                 result_ = { false, "glTF import failed." };
519                 done_ = true;
520                 return;
521             } else if (cancelled_) {
522                 CORE_LOG_V("Importing of '%s' cancelled", GetUri().c_str());
523                 CreateDummyEntity();
524                 result_ = { false, "glTF import cancelled." };
525                 done_ = true;
526                 return;
527             }
528             // Loading and importing of glTF was done successfully. Fill the collection with all the gltf entities.
529             const auto originalRootEntity = ImportSceneFromGltf({});
530             auto* originalRootNode = nodeSystem->GetNode(originalRootEntity);
531 
532             // It's possible to only add some specific node from the gltf.
533             auto* loadNode = originalRootNode;
534             if (!entityPath.empty()) {
535                 loadNode = originalRootNode->LookupNodeByPath(entityPath);
536                 if (!loadNode || loadNode->GetEntity() == Entity {}) {
537                     CORE_LOG_E("Entity '%s' not found from '%s'", entityPath.c_str(), GetUri().c_str());
538                 }
539             }
540 
541             if (!loadNode || loadNode->GetEntity() == Entity {}) {
542                 CreateDummyEntity();
543                 result_ = { false, "Ivalid uri" };
544                 done_ = true;
545                 return;
546             }
547 
548             Entity entity = loadNode->GetEntity();
549             if (entity != Entity {}) {
550                 EntityReference ref = ecs_.GetEntityManager().GetReferenceCounted(entity);
551                 ec_.AddEntity(ref);
552                 ec_.SetId("/", ref);
553                 loadNode->SetParent(nodeSystem->GetRootNode());
554                 for (auto* child : loadNode->GetChildren()) {
555                     AddNodeToCollectionRecursive(ec_, *child, "/");
556                 }
557             }
558             // a little backwards to first create everything and then delete the extra.
559             if (entity != originalRootEntity) {
560                 auto* oldRoot = nodeSystem->GetNode(originalRootEntity);
561                 CORE_ASSERT(oldRoot);
562                 if (oldRoot) {
563                     nodeSystem->DestroyNode(*oldRoot);
564                 }
565             }
566 
567             // Add all resources in separate sub-collections. Not just 3D nodes.
568             {
569                 const auto& importResult = importer_->GetResult();
570                 ec_.AddSubCollection("images", {}).AddEntities(importResult.data.images);
571 
572                 auto& materialCollection = ec_.AddSubCollection("materials", {});
573                 materialCollection.AddEntities(importResult.data.materials);
574 
575                 auto& meshCollection = ec_.AddSubCollection("meshes", {});
576                 meshCollection.AddEntities(importResult.data.meshes);
577 
578                 // Save names of materials and meshes so inserting new ones do not break
579                 // changes done in the editor.
580                 auto* ncm = GetManager<INameComponentManager>(ecs_);
581 
582                 for (const auto& materialEntity : importResult.data.materials) {
583                     if (auto nameComponent = ncm->Read(materialEntity)) {
584                         materialCollection.SetId(nameComponent->name, materialEntity);
585                     }
586                 }
587 
588                 for (const auto& meshEntity : importResult.data.meshes) {
589                     if (auto nameComponent = ncm->Read(meshEntity)) {
590                         meshCollection.SetId(nameComponent->name, meshEntity);
591                     }
592                 }
593 
594                 ec_.AddSubCollection("skins", {}).AddEntities(importResult.data.skins);
595                 ec_.AddSubCollection("animations", {}).AddEntities(importResult.data.animations);
596 
597                 // don't list duplicates
598                 vector<EntityReference> animationTracks;
599                 auto* acm = GetManager<IAnimationComponentManager>(ecs_);
600                 if (acm) {
601                     for (auto& current : importResult.data.animations) {
602                         if (auto handle = acm->Read(current); handle) {
603                             const auto& tracks = handle->tracks;
604                             for (auto& entityRef : tracks) {
605                                 animationTracks.emplace_back(entityRef);
606                             }
607                         }
608                     }
609                 }
610                 ec_.AddSubCollection("animationTracks", {}).AddEntities(animationTracks);
611             }
612 
613             // Load finished successfully.
614             done_ = true;
615         }
616     }
617 
LoadImageEntityCollection()618     bool LoadImageEntityCollection()
619     {
620         auto uri = GetUri();
621         auto imageHandle = assetManager_.GetEcsSerializer().LoadImageResource(uri);
622 
623         // NOTE: Creating the entity even when the image load failed to load so we can detect a missing resource.
624         EntityReference entity = ecs_.GetEntityManager().CreateReferenceCounted();
625         ec_.AddEntity(entity);
626 
627         auto* ncm = GetManager<INameComponentManager>(ecs_);
628         auto* ucm = GetManager<IUriComponentManager>(ecs_);
629         auto* gcm = GetManager<IRenderHandleComponentManager>(ecs_);
630         if (ncm && ucm && gcm) {
631             {
632                 ncm->Create(entity);
633                 auto nc = ncm->Get(entity);
634                 nc.name = PathUtil::GetFilename(uri);
635                 ncm->Set(entity, nc);
636             }
637 
638             {
639                 ucm->Create(entity);
640                 auto uc = ucm->Get(entity);
641                 uc.uri = uri;
642                 ucm->Set(entity, uc);
643             }
644 
645             gcm->Create(entity);
646             if (imageHandle) {
647                 auto ic = gcm->Get(entity);
648                 ic.reference = imageHandle;
649                 gcm->Set(entity, ic);
650 
651                 done_ = true;
652                 return true;
653             }
654         }
655 
656         // NOTE: Always returning true as even when this fails a placeholder entity is created.
657         CORE_LOG_E("Error creating image '%s'", uri.c_str());
658         CreateDummyEntity();
659         result_ = { false, "Error creating image" };
660         done_ = true;
661         return true;
662     }
663 
664 private:
665     IEcsAssetManager& assetManager_;
666     RENDER_NS::IRenderContext& renderContext_;
667     CORE3D_NS::IGraphicsContext& graphicsContext_;
668 
669     CORE_NS::IEcs& ecs_;
670     IEntityCollection& ec_;
671     BASE_NS::string src_;
672     BASE_NS::string contextUri_;
673 
674     IEcsAssetLoader::Result result_ {};
675 
676     vector<EcsAssetLoader::Ptr> dependencies_;
677 
678     bool done_ { false };
679     bool cancelled_ { false };
680     bool async_ { false };
681 
682     GLTFLoadResult loadResult_ {};
683     IGLTF2Importer::Ptr importer_ {};
684 
685     vector<IEcsAssetLoader::IListener*> listeners_;
686 };
687 
CreateEcsAssetLoader(IEcsAssetManager & assetManager,IGraphicsContext & graphicsContext,IEntityCollection & ec,string_view src,string_view contextUri)688 IEcsAssetLoader::Ptr CreateEcsAssetLoader(IEcsAssetManager& assetManager, IGraphicsContext& graphicsContext,
689     IEntityCollection& ec, string_view src, string_view contextUri)
690 {
691     return IEcsAssetLoader::Ptr { new EcsAssetLoader(assetManager, graphicsContext, ec, src, contextUri) };
692 }
693 
694 ECS_SERIALIZER_END_NAMESPACE()
695