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