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#include <algorithm>
16#include <functional>
17#include <scene_plugin/api/camera_uid.h>
18#include <scene_plugin/api/environment_uid.h>
19#include <scene_plugin/api/light_uid.h>
20#include <scene_plugin/api/material_uid.h>
21#include <scene_plugin/api/mesh_uid.h>
22#include <scene_plugin/api/node_uid.h>
23#include <scene_plugin/api/scene_uid.h>
24#include <scene_plugin/api/view_node_uid.h>
25#include <scene_plugin/interface/intf_ecs_scene.h>
26#include <scene_plugin/interface/intf_environment.h>
27
28#include <3d/ecs/systems/intf_node_system.h>
29#include <core/io/intf_file_manager.h>
30
31#include <meta/api/event_handler.h>
32#include <meta/ext/object_container.h>
33#include <meta/interface/animation/intf_animation_controller.h>
34#include <meta/interface/intf_content.h>
35#include <meta/interface/intf_named.h>
36#include <meta/interface/intf_object_hierarchy_observer.h>
37#include <meta/interface/serialization/intf_importer.h>
38
39#include "intf_node_private.h"
40#include "intf_resource_private.h"
41#include "scene_holder.h"
42#include "task_utils.h"
43
44// "synchronously initialize the nodes from main scene file when the scene is loaded
45
46// Save the project .scene file when scene object is being serialized. In practice this may override the changes on
47// scene file from other sources. To be rectified further
48
49// Name prefab instances with IObject::GetUid(). The other option is to use fixed "prefab_instance_NN" with running
50// instance number. The latter may cause trouble with .scene-files if it contains previous instances
51
52using SCENE_NS::MakeTask;
53namespace {
54class NotifyOnExit {
55public:
56    explicit NotifyOnExit(bool notify, std::function<void()> callback) : notify_(notify), callback_(callback) {}
57    virtual ~NotifyOnExit()
58    {
59        if (notify_) {
60            callback_();
61        }
62    }
63    bool notify_;
64    std::function<void()> callback_;
65};
66
67class SceneImpl final : public META_NS::ObjectContainerFwd<SceneImpl, SCENE_NS::ClassId::Scene, SCENE_NS::IScene,
68                            SCENE_NS::IEcsScene, META_NS::IContent, META_NS::IAnimationController> {
69    /// Add the animation controller stuff here. (as scene should be the controller of it's animations)
70    META_FORWARD_READONLY_PROPERTY(uint32_t, Count, animationController_->Count())
71    META_FORWARD_READONLY_PROPERTY(uint32_t, RunningCount, animationController_->RunningCount())
72
73    BASE_NS::vector<BASE_NS::weak_ptr<META_NS::IAnimation>> GetAnimations() const override;
74    BASE_NS::vector<BASE_NS::weak_ptr<META_NS::IAnimation>> GetRunning() const override;
75    bool AddAnimation(const BASE_NS::shared_ptr<META_NS::IAnimation>& animation) override;
76    bool RemoveAnimation(const BASE_NS::shared_ptr<META_NS::IAnimation>& animation) override;
77    void Clear() override;
78    StepInfo Step(const META_NS::IClock::ConstPtr& clock) override;
79    // now back to normal scene impl
80
81    META_IMPLEMENT_INTERFACE_PROPERTY(META_NS::INamed, BASE_NS::string, Name, {})
82    META_IMPLEMENT_INTERFACE_PROPERTY(
83        SCENE_NS::IScene, BASE_NS::string, SystemGraphUri, "project://assets/config/system_graph.json")
84    META_IMPLEMENT_INTERFACE_READONLY_PROPERTY(
85        SCENE_NS::IScene, uint32_t, Status, SCENE_STATUS_UNINITIALIZED, META_NS::DEFAULT_PROPERTY_FLAGS_NO_SER)
86    META_IMPLEMENT_INTERFACE_READONLY_PROPERTY(SCENE_NS::IScene, SCENE_NS::INode::Ptr, RootNode, {})
87    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IScene, SCENE_NS::ICamera::Ptr, DefaultCamera, {})
88    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IScene, BASE_NS::string, Uri, {})
89    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IScene, bool, Asynchronous, false)
90    META_IMPLEMENT_INTERFACE_ARRAY_PROPERTY(SCENE_NS::IScene, SCENE_NS::IMaterial::Ptr, Materials, {})
91    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IScene, SCENE_NS::IRenderConfiguration::Ptr, RenderConfiguration, {})
92
93    META_IMPLEMENT_EVENT(META_NS::IOnChanged, OnLoaded)
94    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IEcsScene, uint8_t, RenderMode, SCENE_NS::IEcsScene::RENDER_IF_DIRTY)
95
96    META_FORWARD_READONLY_PROPERTY(IObject::Ptr, Content, (contentImpl_ ? contentImpl_->Content() : nullptr))
97    META_FORWARD_PROPERTY(bool, ContentSearchable, (contentImpl_ ? contentImpl_->ContentSearchable() : nullptr))
98    META_FORWARD_PROPERTY(
99        META_NS::IContentLoader::Ptr, ContentLoader, (contentImpl_ ? contentImpl_->ContentLoader() : nullptr))
100
101    void SetRenderMode()
102    {
103        AddEngineTask(MakeTask(
104                          [renderMode = META_NS::GetValue(RenderMode())](auto sceneHolder) {
105                              sceneHolder->SetRenderMode(renderMode);
106                              return false;
107                          },
108                          sceneHolder_),
109            false);
110    }
111
112    uint64_t GetCameraHandle(const SCENE_NS::ICamera::Ptr& camera)
113    {
114        if (camera) {
115            auto ecsObject = interface_pointer_cast<SCENE_NS::IEcsObject>(camera);
116            if (ecsObject) {
117                return ecsObject->GetEntity().id;
118            }
119        }
120
121        return SCENE_NS::IScene::DEFAULT_CAMERA;
122    }
123
124    struct BitmapInfo {
125        BitmapInfo() = default;
126        BitmapInfo(SCENE_NS::IBitmap::Ptr bmp) : bitmap(bmp) {}
127        BASE_NS::Math::UVec2 size {};
128        SCENE_NS::IBitmap::Ptr bitmap {};
129        META_NS::EventHandler bitmapChanged {};
130    };
131    BASE_NS::unordered_map<uint64_t, BitmapInfo> bitmaps_;
132    BitmapInfo& GetData(const SCENE_NS::ICamera::Ptr& camera)
133    {
134        auto cameraHandle = GetCameraHandle(camera);
135        if (cameraHandle == SCENE_NS::IScene::DEFAULT_CAMERA) {
136            cameraHandle = defaultCameraHandle_;
137        }
138        auto handle = cameraHandle;
139        if (cameraHandle == SCENE_NS::IScene::DEFAULT_CAMERA && !bitmaps_.empty()) {
140            handle = defaultCameraHandle_;
141        }
142        return bitmaps_[handle];
143    }
144
145    void SetBitmap(const SCENE_NS::IBitmap::Ptr& bitmap, const SCENE_NS::ICamera::Ptr& camera) override
146    {
147        BitmapInfo& data = GetData(camera);
148        auto uiBitmap = interface_pointer_cast<SCENE_NS::IBitmap>(bitmap);
149        if (!uiBitmap) {
150            // disable bitmap override.
151            data.bitmapChanged.Unsubscribe();
152            data.bitmap = {};
153            sceneHolder_->SetCameraTarget(camera, data.size, {});
154            return;
155        }
156
157        data.bitmap = uiBitmap;
158        data.size = data.bitmap->Size()->GetValue();
159        const auto rh = data.bitmap->GetRenderHandle();
160
161        data.bitmapChanged.Subscribe(
162            uiBitmap->ResourceChanged(), META_NS::MakeCallback<META_NS::IOnChanged>([this, camera]() {
163                BitmapInfo& data = GetData(camera);
164                data.size = data.bitmap->Size()->GetValue();
165                const auto rh = data.bitmap->GetRenderHandle();
166                sceneHolder_->SetCameraTarget(camera, data.size, rh);
167            }));
168
169        META_NS::Invoke<META_NS::IOnChanged>(uiBitmap->ResourceChanged());
170    }
171
172    SCENE_NS::IBitmap::Ptr GetBitmap(bool notifyFrameDrawn, const SCENE_NS::ICamera::Ptr& camera) override
173    {
174        BitmapInfo& data = GetData(camera);
175        if (!data.bitmap) {
176            // there is no bitmap for this.
177            //  create it?
178        }
179        return data.bitmap;
180    }
181
182#define CREATE_META_INSTANCE(type, name) GetObjectRegistry().Create<META_NS::type>(META_NS::ClassId::name)
183
184    // META_NS::IContent
185    bool SetContent(const META_NS::IObject::Ptr& content) override
186    {
187        // Should be no-op because the Root Node of a scene (content in this case) can be assigned only if the scene was
188        // reloaded.
189        return false;
190    }
191
192    bool Build(const META_NS::IMetadata::Ptr& data) override
193    {
194        auto& registry = GetObjectRegistry();
195
196        animationController_ = registry.Create<META_NS::IAnimationController>(META_NS::ClassId::AnimationController);
197
198        using IntfPtr = BASE_NS::shared_ptr<CORE_NS::IInterface>;
199        BASE_NS::shared_ptr<RENDER_NS::IRenderContext> rc;
200        META_NS::ITaskQueue::Ptr appQueue;
201        META_NS::ITaskQueue::Ptr engineQueue;
202
203        if (data) {
204            if (auto prp = data->GetPropertyByName<IntfPtr>("RenderContext")) {
205                rc = interface_pointer_cast<RENDER_NS::IRenderContext>(prp->GetValue());
206            }
207            if (auto prp = data->GetPropertyByName<IntfPtr>("EngineQueue")) {
208                engineQueue = interface_pointer_cast<META_NS::ITaskQueue>(prp->GetValue());
209            }
210            if (auto prp = data->GetPropertyByName<IntfPtr>("AppQueue")) {
211                appQueue = interface_pointer_cast<META_NS::ITaskQueue>(prp->GetValue());
212            }
213        }
214        if ((!rc) || (!engineQueue) || (!appQueue)) {
215            return false;
216        }
217
218        hierarchyController_ =
219            registry.Create<META_NS::IObjectHierarchyObserver>(SCENE_NS::ClassId::NodeHierarchyController);
220
221        contentImpl_ = registry.Create<META_NS::IContent>(META_NS::ClassId::ContentObject);
222        if (const auto req = interface_cast<META_NS::IRequiredInterfaces>(contentImpl_)) {
223            req->SetRequiredInterfaces({ SCENE_NS::INode::UID });
224        }
225
226        sceneHolder_.reset(new SceneHolder(GetInstanceId(), registry, rc, appQueue, engineQueue));
227        sceneHolder_->SetOperationMode(Asynchronous()->GetValue());
228
229        asyncChangedToken_ = Asynchronous()->OnChanged()->AddHandler(META_NS::MakeCallback<META_NS::IOnChanged>(
230            [](const auto& sceneHolder, const auto& async) {
231                if (sceneHolder && async) {
232                    sceneHolder->SetOperationMode(async->GetValue());
233                }
234            },
235            sceneHolder_, Asynchronous()));
236
237        renderModeChangedToken_ = RenderMode()->OnChanged()->AddHandler(
238            META_NS::MakeCallback<META_NS::IOnChanged>([weak = BASE_NS::weak_ptr(GetSelf())]() {
239                if (auto self = static_pointer_cast<SceneImpl>(weak.lock())) {
240                    self->SetRenderMode();
241                }
242            }));
243
244        sceneHolder_->SetInitializeCallback(
245            META_NS::MakeCallback<SceneHolder::ISceneInitialized>(
246                [me = BASE_NS::weak_ptr(GetSelf())](const BASE_NS::string& rootId, const BASE_NS::string& cameraId) {
247                    if (auto self = me.lock().get())
248                        static_cast<SceneImpl*>(self)->onSceneInitialized(rootId, cameraId);
249                }),
250            sceneHolder_);
251        sceneHolder_->SetSceneLoadedCallback(
252            META_NS::MakeCallback<SceneHolder::ISceneLoaded>([me = BASE_NS::weak_ptr(GetSelf())](uint32_t status) {
253                if (auto self = me.lock().get())
254                    static_cast<SceneImpl*>(self)->OnSceneLoaded(status);
255            }),
256            sceneHolder_);
257
258        sceneHolder_->Initialize(sceneHolder_);
259        sceneHolder_->SetSystemGraphUri(META_NS::GetValue(SystemGraphUri()));
260        SubscribeToPropertyChanges();
261
262        return true;
263    }
264
265    // this is not usually needed as explicit action, but the scene will pickup changes from
266    // ScenePresenters on the fly
267    void OnCameraChanged()
268    {
269        // Async, sceneholder takes over
270        if (sceneHolder_) {
271            sceneHolder_->ChangeCamera(META_ACCESS_PROPERTY(DefaultCamera)->GetValue());
272        }
273    }
274
275    // Async, sceneholder takes over
276    BASE_NS::vector<META_NS::IAnimation::Ptr> allAnims_;
277    BASE_NS::vector<META_NS::IAnimation::Ptr> GetAnimations() override
278    {
279        // adds/removes animations to cache..
280        auto tmp = allAnims_;
281        allAnims_.clear();
282        for (auto anim : sceneHolder_->GetAnimations()) {
283            if (auto i = interface_cast<META_NS::IObject>(anim)) {
284                auto name = i->GetName();
285
286                size_t ix = 0;
287                if (!RollOverPrefix(ix, name, ANIMATIONS_PREFIX)) {
288                    continue;
289                }
290
291                // check cache
292                META_NS::IAnimation::Ptr rn;
293                auto subname = name.substr(ix);
294                for (auto& a : tmp) {
295                    if (interface_cast<META_NS::IObject>(a)->GetName() == subname) {
296                        rn = a;
297                        break;
298                    }
299                }
300                if (!rn) {
301                    rn = interface_pointer_cast<META_NS::IAnimation>(
302                        CreateNode(name.substr(ix), false, SCENE_NS::ClassId::Animation.Id(),
303                            SCENE_NS::INode::BuildBehavior::NODE_BUILD_CHILDREN_NO_BUILD));
304                }
305
306                allAnims_.push_back(rn);
307            }
308        }
309        return allAnims_;
310    }
311
312    META_NS::IAnimation::Ptr GetAnimation(const BASE_NS::string_view name) override
313    {
314#ifndef USE_DIRECT_ECS_ANIMATION
315
316        size_t ix = 0;
317        if (!RollOverPrefix(ix, name, ANIMATIONS_PREFIX)) {
318            return { nullptr };
319        }
320
321        // check cache
322        if (auto it = animations_.find(name.substr(ix)); it != animations_.end()) {
323            if (auto animation = it->second.lock()) {
324                return animation;
325            }
326        }
327        // Create EcsObject. When running asynchronously, we have no way of knowing if we can rely that existing
328        // animation will be found
329        auto anim = interface_pointer_cast<META_NS::IAnimation>(CreateNode(name.substr(ix), false,
330            SCENE_NS::ClassId::Animation.Id(), SCENE_NS::INode::BuildBehavior::NODE_BUILD_CHILDREN_NO_BUILD));
331
332        return anim;
333#else
334        auto ecsAnimation = sceneHolder_->GetAnimation(BASE_NS::string(name.data(), name.size()));
335
336        if (!ecsAnimation) {
337            ecsAnimation = GetObjectRegistry().Create<SCENE_NS::IEcsAnimation>(SCENE_NS::ClassId::EcsAnimation);
338            AddEngineTask(
339                META_NS::MakeCallable<META_NS::ITaskQueueTask>(
340                    [ecsAnimation, nameString = BASE_NS::string(name.data(), name.size()),
341                        weak = BASE_NS::weak_ptr(sceneHolder_)]() {
342                        if (auto sceneHolder = weak.lock()) {
343                            CORE_NS::Entity entity;
344                            if (sceneHolder->FindAnimation(nameString, entity)) {
345                                if (auto ecsProxyIf = interface_pointer_cast<SCENE_NS::IEcsProxyObject>(ecsAnimation)) {
346                                    ecsProxyIf->SetCommonListener(sceneHolder->GetCommonEcsListener());
347                                }
348                                ecsAnimation->SetEntity(*sceneHolder->GetEcs(), entity);
349                            }
350                        }
351                        return false;
352                    }),
353                false);
354        }
355        META_ACCESS_PROPERTY(Animations)->Get()->Add(ecsAnimation);
356
357        return interface_pointer_cast<META_NS::IAnimation>(ecsAnimation);
358#endif
359    }
360
361    void CreateEmpty() override
362    {
363        Load("scene://empty");
364    }
365
366    bool Load(const BASE_NS::string_view uri) override
367    {
368        if (!Name()->IsValueSet()) {
369            SetValue(Name(), "Scene");
370        }
371
372        if (!uri.empty()) {
373            META_ACCESS_PROPERTY(Status)->SetValue(SCENE_STATUS_LOADING);
374            sceneHolder_->Load(BASE_NS::string(uri.data(), uri.size()));
375            return true;
376        }
377        return false;
378    }
379
380    void onSystemGraphUriChanged()
381    {
382        META_ACCESS_PROPERTY(Status)->SetValue(SCENE_STATUS_LOADING);
383        sceneHolder_->SetSystemGraphUri(META_NS::GetValue(SystemGraphUri()));
384    }
385
386    void SetRenderSize(uint32_t width, uint32_t height, const SCENE_NS::ICamera::Ptr& camera) override
387    {
388        if (camera) {
389            camera->SetDefaultRenderTargetSize(width, height);
390        } else if (auto defaultCamera = META_NS::GetValue(DefaultCamera())) {
391            defaultCamera->SetDefaultRenderTargetSize(width, height);
392        }
393    }
394
395    SCENE_NS::INode::Ptr GetNode(const BASE_NS::string_view path, const BASE_NS::Uid classId,
396        SCENE_NS::INode::BuildBehavior buildBehavior) override
397    {
398        return GetNodeRecursive(path, classId, true, buildBehavior);
399    }
400
401    META_NS::ObjectId ResolveNodeTypeFromPath(const BASE_NS::string_view patchedPath, bool isNodeType)
402    {
403        // This is best effort
404        // We cannot determine the type unless ECS has probed the component
405
406        // This kind of introspection may cause materials and meshes to be treated as nodes
407        // which kind of contradicts with their normal use through API
408        auto ecs = GetEcs();
409        CORE3D_NS::INodeSystem& nodeSystem = *CORE_NS::GetSystem<CORE3D_NS::INodeSystem>(*ecs);
410        const auto& root = nodeSystem.GetRootNode();
411
412        const auto& ecsNode = root.LookupNodeByPath(patchedPath);
413        CORE_NS::Entity entity {};
414        if (ecsNode) {
415            entity = ecsNode->GetEntity();
416        } else {
417            CORE_LOG_W("%s:No entity for %s, type info not available", __func__, BASE_NS::string(patchedPath).c_str());
418            CORE_LOG_W("If you know the expected type, consider using a template or providing the class id");
419        }
420
421        // shadow camera could provide false positive, so order matters
422        if (auto lightManager = ecs->GetComponentManager(CORE3D_NS::ILightComponentManager::UID)) {
423            if (lightManager->HasComponent(entity)) {
424                return SCENE_NS::ClassId::Light;
425            }
426        }
427
428        if (auto cameraManager = ecs->GetComponentManager(CORE3D_NS::ICameraComponentManager::UID)) {
429            if (cameraManager->HasComponent(entity)) {
430                return SCENE_NS::ClassId::Camera;
431            }
432        }
433
434        if (auto envManager = ecs->GetComponentManager(CORE3D_NS::IEnvironmentComponentManager::UID)) {
435            if (envManager->HasComponent(entity)) {
436                return SCENE_NS::ClassId::Environment;
437            }
438        }
439
440        if (!isNodeType) {
441            if (auto nodeManager = ecs->GetComponentManager(CORE3D_NS::INodeComponentManager::UID)) {
442                // quirk, prefer nodes with node component treated as node)
443                if (!nodeManager->HasComponent(entity)) {
444                    if (auto meshManager = ecs->GetComponentManager(CORE3D_NS::IMeshComponentManager::UID)) {
445                        if (meshManager->HasComponent(entity)) {
446                            return SCENE_NS::ClassId::Mesh;
447                        }
448                    }
449                    if (auto materialManager = ecs->GetComponentManager(CORE3D_NS::IMaterialComponentManager::UID)) {
450                        if (materialManager->HasComponent(entity)) {
451                            return SCENE_NS::ClassId::Material;
452                        }
453                    }
454                }
455            }
456        }
457
458        return SCENE_NS::ClassId::Node;
459    }
460
461    SCENE_NS::INode::Ptr GetNodeRecursive(const BASE_NS::string_view path, const META_NS::ObjectId classId,
462        bool recurse, SCENE_NS::INode::BuildBehavior buildBehavior)
463    {
464        if (path.empty() || path == "/") {
465            return SCENE_NS::INode::Ptr {};
466        }
467
468        BASE_NS::string patchedPath = recurse ? NormalizePath(path) : BASE_NS::string(path.data(), path.size());
469        if (auto ite = nodes_.find(patchedPath) != nodes_.cend()) {
470            return nodes_[patchedPath];
471        }
472
473        // ensure parent objects exist
474        if (recurse) {
475            size_t ix = patchedPath.find('/', 1);
476            while (BASE_NS::string_view::npos != ix) {
477                auto substr = patchedPath.substr(0, ix);
478                // When we traverse up the tree, we must ensure that the object is a node.
479                currentParent_ = GetNodeRecursive(substr, SCENE_NS::INode::UID, false, buildBehavior);
480                ++ix;
481                ix = patchedPath.find('/', ix);
482            }
483        }
484
485        auto ecs = GetEcs();
486        META_NS::ObjectId implementationId = SCENE_NS::ClassId::Node;
487
488        bool isNodeType = (classId == SCENE_NS::INode::UID);
489        if ((classId == META_NS::IObject::UID || isNodeType) && ecs) {
490            implementationId = ResolveNodeTypeFromPath(patchedPath, isNodeType);
491
492        } else {
493            implementationId = classId;
494        }
495
496        auto node = CreateNode(patchedPath, false, implementationId, buildBehavior);
497
498        currentParent_ = {};
499        return node; // finalInterface;
500    }
501
502    void RemoveNodeFromCurrentContainer(SCENE_NS::INode::Ptr& node)
503    {
504        if (auto containable = interface_cast<META_NS::IContainable>(node)) {
505            if (auto parent = interface_pointer_cast<META_NS::IContainer>(containable->GetParent())) {
506                parent->Remove(node);
507            }
508        }
509    }
510
511    bool RollOverPrefix(size_t& ix, const BASE_NS::string_view& name, const BASE_NS::string_view& prefix)
512    {
513        while (ix < name.length() && name[ix] == '/') {
514            ix++;
515        }
516
517        if (name.substr(ix).find(prefix) == 0) {
518            ix += prefix.length();
519        }
520
521        while (ix < name.length() && name[ix] == '/') {
522            ix++;
523        }
524
525        return ix < name.length();
526    }
527
528    void AddMaterial(SCENE_NS::IMaterial::Ptr material) override
529    {
530        auto materials = Materials()->GetValue();
531        auto it = std::find(materials.begin(), materials.end(), material);
532        if (it != materials.end()) {
533            // Already exists.
534            CORE_LOG_D("Trying to add same material to scene multiple times.");
535            return;
536        }
537
538        Materials()->AddValue(material);
539        UpdateCachedReference(interface_pointer_cast<SCENE_NS::INode>(material));
540    }
541
542    void RemoveMaterial(SCENE_NS::IMaterial::Ptr material) override
543    {
544        auto lock = Materials().GetLockedAccess();
545        auto vec = lock->GetValue();
546        for (size_t index = 0; index != vec.size(); ++index) {
547            if (vec[index] == material) {
548                lock->RemoveAt(index);
549                break;
550            }
551        }
552
553        for (auto&& ite : materials_) {
554            if (ite.second.lock() == material) {
555                ReleaseMaterial(ite.first);
556                break;
557            }
558        }
559    }
560
561    BASE_NS::vector<SCENE_NS::ICamera::Ptr> GetCameras() const override
562    {
563        BASE_NS::vector<SCENE_NS::ICamera::Ptr> result;
564        for (auto c : cameras_) {
565            if (auto cam = c.second.lock()) {
566                result.push_back(cam);
567            }
568        }
569        return result;
570    }
571
572    BASE_NS::vector<SCENE_NS::IMaterial::Ptr> GetMaterials() const override
573    {
574        BASE_NS::vector<SCENE_NS::IMaterial::Ptr> result;
575        for (auto& material : materials_) {
576            auto ptr = material.second.lock();
577            if (ptr) {
578                result.push_back(ptr);
579            }
580        }
581
582        return result;
583    }
584
585    // Returns a material from the scene with a given path
586    SCENE_NS::IMaterial::Ptr GetMaterial(const BASE_NS::string_view name) override
587    {
588        // The material file, aka uri-path is somewhat parallel due ownership is different for uri-materials
589        // however, one can claim traditional handle and have them preserved
590        // through flat cache. Should be consolidated someday.
591        if (name.find("://") != BASE_NS::string_view::npos) {
592            return GetOrLoadMaterial(name);
593        }
594
595        size_t ix = 0;
596        if (!RollOverPrefix(ix, name, MATERIALS_PREFIX)) {
597            return { nullptr };
598        }
599
600        // check cache (first with name:entityid)
601        if (auto it = materials_.find(name.substr(ix)); it != materials_.end()) {
602            if (auto material = it->second.lock()) {
603                return material;
604            }
605        }
606
607        // check cache (direct material name)
608        for (auto entry : materials_) {
609            auto material = entry.second.lock();
610            if (auto node = interface_pointer_cast<SCENE_NS::INode>(material)) {
611                if (node->Name()->GetValue() == name) {
612                    return material;
613                }
614            }
615        }
616
617        if (auto it = materials_.find(name.substr(ix)); it != materials_.end()) {
618            if (auto material = it->second.lock()) {
619                return material;
620            }
621        }
622
623        // Create EcsObject. When running asynchronously, we have no way of knowing if we should create
624        // new node or just rely that existing will be found
625        auto mat = interface_pointer_cast<SCENE_NS::IMaterial>(CreateNode(name.substr(ix), false,
626            SCENE_NS::ClassId::Material.Id(), SCENE_NS::INode::BuildBehavior::NODE_BUILD_CHILDREN_NO_BUILD));
627
628        return mat;
629    }
630
631    BASE_NS::vector<SCENE_NS::IMesh::Ptr> GetMeshes() const override
632    {
633        BASE_NS::vector<SCENE_NS::IMesh::Ptr> result;
634        for (auto mesh : meshes_) {
635            auto ptr = mesh.second.lock();
636            if (ptr) {
637                result.push_back(ptr);
638            }
639        }
640
641        return result;
642    }
643
644    // Returns a material from the scene with a given name
645    SCENE_NS::IMesh::Ptr GetMesh(const BASE_NS::string_view name) override
646    {
647        size_t ix = 0;
648        if (!RollOverPrefix(ix, name, MESHES_PREFIX)) {
649            return { nullptr };
650        }
651
652        // check cache
653        if (auto it = meshes_.find(name.substr(ix)); it != meshes_.end()) {
654            if (auto mesh = it->second.lock()) {
655                return mesh;
656            }
657        }
658        // Create EcsObject. When running asynchronously, we have no way of knowing if we should create
659        // new node or just rely that existing will be found
660        auto mesh = interface_pointer_cast<SCENE_NS::IMesh>(CreateNode(name.substr(ix), false,
661            SCENE_NS::ClassId::Mesh.Id(), SCENE_NS::INode::BuildBehavior::NODE_BUILD_CHILDREN_NO_BUILD));
662
663        return mesh;
664    }
665
666    BASE_NS::string constructPath(CORE_NS::Entity ent) const
667    {
668        auto ecs = GetEcs();
669        auto* nodeManager = CORE_NS::GetManager<CORE3D_NS::INodeComponentManager>(*ecs);
670        auto* nameManager = CORE_NS::GetManager<CORE3D_NS::INameComponentManager>(*ecs);
671        BASE_NS::string path;
672        if (nodeManager && nameManager) {
673            auto curent = ent;
674            for (;;) {
675                if (!CORE_NS::EntityUtil::IsValid(curent)) {
676                    // Reached root.
677                    break;
678                }
679                if (!nodeManager->HasComponent(curent)) {
680                    // not a node?
681                    return "";
682                }
683                if (!nameManager->HasComponent(curent)) {
684                    // no name in hierarchy. "fail"? or generate "name" for path..
685                    return "";
686                }
687                auto namecomp = nameManager->Get(curent);
688                if (!path.empty()) {
689                    path.insert(0, "/");
690                }
691                path.insert(0, namecomp.name.c_str());
692                const auto& node = nodeManager->Get(curent);
693                curent = node.parent;
694            }
695        }
696        if (!path.empty()) {
697            path.insert(0, "/");
698        }
699        return path;
700    }
701    // Implementation for scene change callbacks.
702    // The tricky bit here is that we may have placeholders on containers that may have received serialized data
703    // during construction, so we just cannot replace those, but need to rebind them instead
704    void onSceneInitialized(const BASE_NS::string& rootId, const BASE_NS::string& cameraId)
705    {
706        // Set root node
707        rootNodeId_ = rootId;
708        auto rootIdNormalized = NormalizePath(rootNodeId_);
709
710        if (rootNodePtr_) {
711            // check if someone assigned us a root node while we are preserving the previous one
712            if (!META_ACCESS_PROPERTY(RootNode)->GetValue()) {
713                META_ACCESS_PROPERTY(RootNode)->SetValue(rootNodePtr_);
714            }
715            rootNodePtr_.reset();
716        }
717
718        if (cameraNodePtr_) {
719            // check if someone assigned us a root node while we are preserving the previous one
720            if (!META_ACCESS_PROPERTY(DefaultCamera)->GetValue()) {
721                META_ACCESS_PROPERTY(DefaultCamera)->SetValue(cameraNodePtr_);
722            }
723            cameraNodePtr_.reset();
724        }
725
726        if (auto renderConfiguration = META_NS::GetValue(RenderConfiguration())) {
727            // Ensure the render configuration is bound to scene.
728            auto resource = interface_pointer_cast<IResourcePrivate>(renderConfiguration);
729            if (resource) {
730                resource->Connect(sceneHolder_);
731            }
732        }
733
734        // setup subscription for toolkit changes
735        RenderConfiguration()->OnChanged()->AddHandler(META_NS::MakeCallback<META_NS::IOnChanged>([this]() {
736            auto resource = interface_pointer_cast<IResourcePrivate>(GetValue(RenderConfiguration()));
737            if (resource) {
738                resource->Connect(sceneHolder_);
739            }
740        }),
741            reinterpret_cast<uint64_t>(this));
742
743        if (auto rootNode = META_ACCESS_PROPERTY(RootNode)->GetValue()) {
744            // switch new ecsObject
745            BindNodeToEcs(rootNode, rootIdNormalized, false);
746            nodes_[rootIdNormalized] = rootNode;
747        } else {
748            META_ACCESS_PROPERTY(RootNode)->SetValue(GetSelf<SCENE_NS::IScene>()->GetNode<SCENE_NS::INode>(rootId));
749        }
750
751        auto defaultCamera = META_ACCESS_PROPERTY(DefaultCamera)->GetValue();
752        if (!defaultCamera) {
753            defaultCamera = GetSelf<SCENE_NS::IScene>()->GetNode<SCENE_NS::ICamera>(cameraId);
754            META_ACCESS_PROPERTY(DefaultCamera)->SetValue(defaultCamera);
755        }
756
757        if (defaultCamera) {
758            ActivateCamera(defaultCamera);
759        }
760
761        // under normal conditions, this would take place asyncronously
762        // however, if we are running on synchronous mode we need to restore the ecs bindings here
763        for (auto& prevNode : nodes_) {
764            if (prevNode.first != rootIdNormalized && prevNode.first != cameraId) {
765                BindNodeToEcs(prevNode.second, prevNode.first, false);
766            }
767        }
768
769        // This is information only unless we instantiate all the scene nodes immediately
770        // Should be flagged more effectively on production builds
771        CORE3D_NS::INodeSystem& nodeSystem = *CORE_NS::GetSystem<CORE3D_NS::INodeSystem>(*GetEcs());
772        const auto& root = nodeSystem.GetRootNode();
773
774        instantiateNodes(root, "/");
775
776        // notify observers about the change
777        META_ACCESS_PROPERTY(Status)->SetValue(SCENE_NS::IScene::SCENE_STATUS_READY);
778        META_NS::Invoke<META_NS::IOnChanged>(OnLoaded());
779
780        sceneHolder_->SetUninitializeCallback(
781            META_NS::MakeCallback<SceneHolder::ISceneUninitialized>([me = BASE_NS::weak_ptr(GetSelf())]() {
782                if (auto self = me.lock().get())
783                    static_cast<SceneImpl*>(self)->DetachScene();
784            }),
785            sceneHolder_);
786    }
787
788    void OnSceneLoaded(uint32_t status)
789    {
790        META_ACCESS_PROPERTY(Status)->SetValue(status);
791        if (status == SCENE_NS::IScene::SCENE_STATUS_READY) {
792            META_NS::Invoke<META_NS::IOnChanged>(OnLoaded());
793        }
794    }
795
796    // Implementation for scene processing.
797
798    void DetachScene(bool setDirty = false)
799    {
800        if (META_ACCESS_PROPERTY(Status)) {
801            META_ACCESS_PROPERTY(Status)->SetValue(SCENE_STATUS_UNINITIALIZED);
802        }
803
804        cameras_.clear();
805        materials_.clear();
806        meshes_.clear();
807        animations_.clear();
808        nodes_.clear();
809
810        // This effectively means that we keep the scene user objects forcibly live
811        // until we get fresh ones to replace them, not sure if that is intended
812        rootNodePtr_ = META_ACCESS_PROPERTY(RootNode)->GetValue();
813        cameraNodePtr_ = META_ACCESS_PROPERTY(DefaultCamera)->GetValue();
814        META_ACCESS_PROPERTY(RootNode)->SetValue({});
815        META_ACCESS_PROPERTY(DefaultCamera)->SetValue({});
816
817        if (setDirty) {
818            for (const auto& bitmap : bitmaps_) {
819                /*auto externalBitmap = interface_pointer_cast<UI_NS::ILume2DExternalBitmap>(bitmap.second.bitmap);
820                if (externalBitmap) {
821                    externalBitmap->SetCoreBitmap(nullptr);
822                }*/
823                bitmap.second.bitmap->SetRenderHandle({}, { 0, 0 });
824            }
825        }
826    }
827
828    void instantiateNodes(const CORE3D_NS::ISceneNode& node, BASE_NS::string path)
829    {
830        if (!path.empty() && path.back() != '/') {
831            path.append("/");
832        }
833        if (!node.GetName().empty()) {
834            path.append(node.GetName());
835        } else {
836#ifndef INSTANTIATE_NODES_ON_INITIALIZE
837            path.append("[");
838            path.append(BASE_NS::to_string(node.GetEntity().id));
839            path.append("]");
840#endif
841        }
842        SCENE_PLUGIN_VERBOSE_LOG("%s", path.c_str());
843#ifdef INSTANTIATE_NODES_ON_INITIALIZE
844        GetNode(path);
845#endif
846
847        for (const auto child : node.GetChildren())
848            instantiateNodes(*child, path);
849    }
850
851    BASE_NS::string NormalizePath(const BASE_NS::string_view& path)
852    {
853        BASE_NS::string patchedPath;
854        if (path == rootNodeId_) {
855            patchedPath = path;
856            patchedPath.insert(0, "/");
857        } else {
858            bool hasSlash = (path[0] == '/');
859            if (path.compare(hasSlash ? 1 : 0, rootNodeId_.size(), rootNodeId_.data())) {
860                SCENE_PLUGIN_VERBOSE_LOG("%s: the path does not contain root node, patching the path", __func__);
861                patchedPath.append("/");
862                patchedPath.append(rootNodeId_);
863                if (!hasSlash) {
864                    patchedPath.append("/");
865                }
866                patchedPath.append(path.data(), path.size());
867            } else {
868                patchedPath = path;
869                if (!hasSlash) {
870                    patchedPath = "/" + patchedPath;
871                }
872            }
873        }
874        return patchedPath;
875    }
876
877    SCENE_NS::IEcsObject::Ptr FindEcsObject(const BASE_NS::string_view& path, BASE_NS::string& patchedPath)
878    {
879        patchedPath = NormalizePath(path);
880
881        if (auto it = nodes_.find(patchedPath); it != nodes_.end()) {
882            return interface_pointer_cast<SCENE_NS::IEcsObject>(it->second);
883        }
884        return SCENE_NS::IEcsObject::Ptr {};
885    }
886
887    SCENE_NS::IEcsObject::Ptr CreateNewEcsObject(const BASE_NS::string& /*path*/)
888    {
889        // Create new helper object
890        // Not much left here, could be presumably removed
891        auto ret = SCENE_NS::IEcsObject::Ptr {};
892
893// Allow construction to progress even ecs was not available,
894// nodes will perform initialization asynchronously anyway
895        if (auto object = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::EcsObject)) {
896            ret = interface_pointer_cast<SCENE_NS::IEcsObject>(object);
897        }
898        return ret;
899    }
900
901    SCENE_NS::IEcsObject::Ptr GetEcsObject(const BASE_NS::string_view& path) override
902    {
903        return interface_pointer_cast<SCENE_NS::IEcsObject>(
904            GetNode(path, META_NS::IObject::UID, SCENE_NS::INode::BuildBehavior::NODE_BUILD_CHILDREN_NO_BUILD));
905    }
906
907    CORE_NS::IEcs::Ptr GetEcs() override
908    {
909        return sceneHolder_->GetEcs();
910    }
911
912    CORE_NS::IEcs::Ptr GetEcs() const
913    {
914        return sceneHolder_->GetEcs();
915    }
916
917    META_NS::ITaskQueue::Token AddEngineTask(
918        const META_NS::ITaskQueue::CallableType::Ptr& task, bool runDeferred) override
919    {
920        if (sceneHolder_) {
921            return sceneHolder_->QueueEngineTask(task, runDeferred);
922        }
923        return META_NS::ITaskQueue::Token {};
924    }
925
926    META_NS::ITaskQueue::Token AddApplicationTask(
927        const META_NS::ITaskQueue::CallableType::Ptr& task, bool runDeferred) override
928    {
929        if (sceneHolder_) {
930            return sceneHolder_->QueueApplicationTask(task, runDeferred);
931        }
932        return META_NS::ITaskQueue::Token {};
933    }
934
935    void CancelEngineTask(META_NS::ITaskQueue::Token token) override
936    {
937        if (sceneHolder_) {
938            return sceneHolder_->CancelEngineTask(token);
939        }
940    }
941
942    void CancelAppTask(META_NS::ITaskQueue::Token token) override
943    {
944        if (sceneHolder_) {
945            return sceneHolder_->CancelAppTask(token);
946        }
947    }
948
949    SCENE_NS::IEntityCollection* GetEntityCollection() override
950    {
951        if (sceneHolder_) {
952            return sceneHolder_->GetEntityCollection();
953        }
954
955        return {};
956    }
957
958    SCENE_NS::IAssetManager* GetAssetManager() override
959    {
960        if (sceneHolder_) {
961            return sceneHolder_->GetAssetManager();
962        }
963
964        return {};
965    }
966
967    SCENE_NS::IMaterial::Ptr GetOrLoadMaterial(BASE_NS::string_view uri)
968    {
969        // check cache
970        if (auto it = uriMaterials_.find(uri); it != uriMaterials_.end()) {
971            if (auto material = it->second.lock()) {
972                return material;
973            }
974        }
975
976        auto ret = LoadMaterial(uri);
977        uriMaterials_[uri] = ret;
978        return ret;
979    }
980
981    SCENE_NS::IMaterial::Ptr LoadMaterial(BASE_NS::string_view uri) override
982    {
983        bool isGltfUri = (uri.find(".glb/materials/") != BASE_NS::string_view::npos) ||
984                         (uri.find(".gltf/materials/") != BASE_NS::string_view::npos);
985        if (isGltfUri) {
986            auto resource = CreateResourceFromUri(SCENE_NS::ClassId::Material, uri);
987            if (resource) {
988                return interface_pointer_cast<SCENE_NS::IMaterial>(resource);
989            }
990
991            CORE_LOG_W("Could not resolve material URI: %s", BASE_NS::string(uri.data(), uri.size()).c_str());
992            // we could return the control to CreateMaterial and let the engine resolve the materials with uri
993            // componenta as they seem to be in place when loaded from glb or gltf
994        }
995
996        SCENE_NS::IMaterial::Ptr ret;
997
998        return ret;
999    }
1000
1001    SCENE_NS::INode::Ptr CreateNode(const META_NS::ObjectId classId)
1002    {
1003        SCENE_NS::INode::Ptr node;
1004
1005        if (classId == SCENE_NS::ClassId::Light.Id() || classId == SCENE_NS::ILight::UID) {
1006            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Light);
1007        } else if (classId == SCENE_NS::ClassId::Camera.Id() || classId == SCENE_NS::ICamera::UID) {
1008            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Camera);
1009        } /*else if (classId == SCENE_NS::ClassId::ViewNode.Id() || classId == SCENE_NS::IViewNode::UID) {
1010            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::ViewNode);
1011        } */
1012        else if (classId == SCENE_NS::ClassId::Material.Id() || classId == SCENE_NS::IMaterial::UID) {
1013            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Material);
1014        } else if (classId == SCENE_NS::ClassId::Mesh.Id() || classId == SCENE_NS::IMesh::UID) {
1015            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Mesh);
1016        } else if (classId == SCENE_NS::ClassId::Animation.Id() || classId == META_NS::IAnimation::UID) {
1017            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Animation);
1018        } else if (classId == SCENE_NS::ClassId::Environment.Id() || classId == SCENE_NS::IEnvironment::UID) {
1019            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Environment);
1020        } else {
1021            if (classId != META_NS::IObject::UID && classId != SCENE_NS::ClassId::Node.Id() &&
1022                classId != SCENE_NS::INode::UID) {
1023                CORE_LOG_W("%s: uid not known, returning INode instance", __func__);
1024            }
1025
1026            node = GetObjectRegistry().Create<SCENE_NS::INode>(SCENE_NS::ClassId::Node);
1027        }
1028
1029        return node;
1030    }
1031
1032    // Quirk / constraints:
1033    // 1) has name -> cached and asynchronously initialized
1034    // 2) name empty, just an object, not sure if needed at the end
1035    SCENE_NS::INode::Ptr CreateNode(const BASE_NS::string_view name, bool createEngineObject,
1036        const META_NS::ObjectId classId, SCENE_NS::INode::BuildBehavior buildBehavior) override
1037    {
1038        if (const auto& ite = nodes_.find(name); ite != nodes_.cend() && createEngineObject) {
1039            CORE_LOG_W("Refusing to create new duplicate node: %s", BASE_NS::string(name.data(), name.size()).c_str());
1040            return ite->second;
1041        }
1042
1043        SCENE_NS::INode::Ptr node = CreateNode(classId);
1044
1045        if (node) {
1046            node->BuildChildren(buildBehavior);
1047            BindNodeToEcs(node, name, createEngineObject);
1048        }
1049        return node;
1050    }
1051
1052    SCENE_NS::INode::Ptr CreateResourceFromUri(const META_NS::ObjectId classId, BASE_NS::string_view uri)
1053    {
1054        SCENE_NS::INode::Ptr node;
1055
1056        auto entity = sceneHolder_->GetEntityByUri(uri);
1057
1058        if (CORE_NS::EntityUtil::IsValid(entity)) {
1059            BASE_NS::string name;
1060            if (sceneHolder_->GetEntityName(entity, name)) {
1061                node = CreateNode(classId);
1062                if (node) {
1063                    auto ecsScene = GetSelf<SCENE_NS::IEcsScene>();
1064                    auto ecsObject = CreateNewEcsObject({});
1065                    auto nodeInterface = interface_pointer_cast<INodeEcsInterfacePrivate>(node);
1066                    nodeInterface->Initialize(ecsScene, ecsObject, {}, "", name, sceneHolder_, entity);
1067                }
1068            }
1069        }
1070
1071        return node;
1072    }
1073
1074    void BindNodeToEcs(
1075        SCENE_NS::INode::Ptr& node, const BASE_NS::string_view fullPath, bool createEngineObject) override
1076    {
1077        SCENE_PLUGIN_VERBOSE_LOG("Scene::BindNodeToEcs called for %s", BASE_NS::string(fullPath).c_str());
1078
1079        CORE_NS::Entity entity;
1080        auto ecsScene = GetSelf<SCENE_NS::IEcsScene>();
1081
1082        bool addToRootContainer { false };
1083        BASE_NS::string nodePath;
1084        BASE_NS::string nodeName;
1085
1086        auto classUid = interface_cast<META_NS::IObject>(node)->GetClassId();
1087        bool isResourceClassType = (classUid == SCENE_NS::ClassId::Material) || // Material
1088                                   (classUid == SCENE_NS::ClassId::Mesh) ||     // Mesh
1089                                   (classUid == SCENE_NS::ClassId::Animation);  // Animation
1090
1091        auto isRealUri = fullPath.find("://") != BASE_NS::string_view::npos;
1092
1093        if (!isRealUri) {
1094            auto cutIx = fullPath.find_last_of('/');
1095            if (cutIx != BASE_NS::string_view::npos && cutIx < fullPath.size()) {
1096                ++cutIx;
1097                nodePath = BASE_NS::string(fullPath.data(), cutIx);
1098                nodeName = BASE_NS::string(fullPath.data() + cutIx, fullPath.size() - cutIx);
1099            } else if (!fullPath.empty()) {
1100                nodeName = BASE_NS::string(fullPath.data(), fullPath.size());
1101            }
1102        }
1103
1104        if (nodeName.empty()) {
1105            if (createEngineObject) {
1106                nodeName = interface_cast<META_NS::IObjectInstance>(node)->GetInstanceId().ToString();
1107            } else {
1108                if (isRealUri) {
1109                    auto ecsObject = CreateNewEcsObject({});
1110                    auto nodeInterface = interface_cast<INodeEcsInterfacePrivate>(node);
1111                    nodeInterface->Initialize(ecsScene, ecsObject, {}, "", BASE_NS::string(fullPath), sceneHolder_, {});
1112                } else {
1113                    CORE_LOG_W("%s: refusing to create proxy object without valid target, name is empty.", __func__);
1114                }
1115                return;
1116            }
1117        }
1118
1119        auto nodeInterface = interface_cast<INodeEcsInterfacePrivate>(node);
1120
1121        if (nodeInterface->EcsObject() && CORE_NS::EntityUtil::IsValid(nodeInterface->EcsObject()->GetEntity())) {
1122            CORE_LOG_W("%s: refusing to recreate proxy object while current one is valid: %s", __func__,
1123                BASE_NS::string(fullPath).c_str());
1124            return;
1125        }
1126
1127        auto ecsObject = CreateNewEcsObject({});
1128
1129            // NEW NODE
1130        if (createEngineObject) {
1131            auto classUid = interface_pointer_cast<META_NS::IObject>(node)->GetClassId();
1132            auto constructNode = [this, nodePath, nodeName, classUid](
1133                                         const auto& sceneHolder) -> CORE_NS::Entity {
1134                    CORE_NS::Entity entity;
1135                if (sceneHolder) {
1136                    // Should not need to type this deep here
1137                    if (classUid == SCENE_NS::ClassId::Material) {
1138                        entity = sceneHolder->CreateMaterial(nodeName);
1139                    } else if (classUid == SCENE_NS::ClassId::Camera) {
1140                        entity = sceneHolder->CreateCamera(nodePath, nodeName, 0);
1141                    } else if (auto node = sceneHolder->CreateNode(nodePath, nodeName)) {
1142                        entity = node->GetEntity();
1143
1144                        sceneHolder->EnableLayerComponent(entity);
1145                        if (classUid == SCENE_NS::ClassId::Light) {
1146                            sceneHolder->EnableLightComponent(entity);
1147                        }
1148                        if (classUid == SCENE_NS::ClassId::Environment) {
1149                            sceneHolder->EnableEnvironmentComponent(entity);
1150                        }
1151                    }
1152                }
1153
1154                return entity;
1155            };
1156            if (GetValue(Asynchronous())) {
1157                AddEngineTask(MakeTask(
1158                                  [constructNode](const auto& sceneHolder) {
1159                                      constructNode(sceneHolder);
1160                                      return false;
1161                                  },
1162                                  sceneHolder_),
1163                    false);
1164            } else {
1165                entity = constructNode(sceneHolder_);
1166            }
1167
1168            addToRootContainer = true;
1169            // If we don't have parent .. then attach to root.
1170            if (nodePath.empty() && !isResourceClassType) {
1171                nodePath = "/" + rootNodeId_ + "/";
1172            }
1173
1174            if (META_NS::Property<uint32_t> creationPolicy = nodeInterface->GetLifecycleInfo()) {
1175                creationPolicy->SetValue(NODE_LC_CREATED);
1176                nodeInterface->ClaimOwnershipOfEntity(true);
1177            }
1178        } else {
1179            // We expect to find the node from ecs (sooner or later)
1180            // If we are running synchronously, we could check it and even
1181            // tell the calling code if the node is there.
1182
1183            // We'd need to know some additional info about the node parent etc
1184            if (META_NS::Property<uint32_t> creationPolicy = nodeInterface->GetLifecycleInfo()) {
1185                creationPolicy->SetValue(NODE_LC_MIRROR_EXISTING);
1186            }
1187        }
1188
1189        SCENE_NS::INode::Ptr parent;
1190
1191        if (!isResourceClassType) {
1192            auto containable = interface_cast<META_NS::IContainable>(node);
1193            if (containable->GetParent()) {
1194                parent = interface_pointer_cast<SCENE_NS::INode>(containable->GetParent());
1195            }
1196
1197            if (!parent) {
1198                parent = addToRootContainer ? RootNode()->GetValue() : currentParent_;
1199            }
1200        }
1201
1202        nodeInterface->Initialize(ecsScene, ecsObject, parent, nodePath, nodeName, sceneHolder_, entity);
1203    }
1204
1205    void UpdateCachedNodePath(const SCENE_NS::INode::Ptr& node) override
1206    {
1207        if (node) {
1208            if (interface_cast<META_NS::IObject>(node)->GetClassId() != SCENE_NS::ClassId::Node) {
1209                UpdateCachedReference(node);
1210                return;
1211            }
1212
1213            bool found = false;
1214
1215            for (auto&& ite : nodes_) {
1216                if (ite.second == node) {
1217                    nodes_.erase(ite.first);
1218                    break;
1219                }
1220            }
1221
1222            BASE_NS::string pathString = node->Path()->GetValue();
1223            pathString.append(node->Name()->GetValue());
1224            nodes_[pathString] = node;
1225        }
1226    }
1227
1228    void SetCacheEnabled(const SCENE_NS::INode::Ptr& node, bool enabled) override
1229    {
1230        if (!node) {
1231            return;
1232        }
1233
1234        if (enabled) {
1235            BASE_NS::string pathString = node->Path()->GetValue();
1236            pathString.append(node->Name()->GetValue());
1237            nodes_[pathString] = node;
1238        } else {
1239            for (auto&& ite : nodes_) {
1240                if (ite.second == node) {
1241                    nodes_.erase(ite.first);
1242                    break;
1243                }
1244            }
1245        }
1246    }
1247
1248    typedef BASE_NS::shared_ptr<CORE_NS::IInterface> (*fun)(SceneImpl* me, const BASE_NS::string_view);
1249
1250    static BASE_NS::shared_ptr<CORE_NS::IInterface> relmat(SceneImpl* me, const BASE_NS::string_view p)
1251    {
1252        return me->ReleaseMaterial(p);
1253    }
1254    static BASE_NS::shared_ptr<CORE_NS::IInterface> relmesh(SceneImpl* me, const BASE_NS::string_view p)
1255    {
1256        return me->ReleaseMesh(p);
1257    }
1258    static BASE_NS::shared_ptr<CORE_NS::IInterface> relanim(SceneImpl* me, const BASE_NS::string_view p)
1259    {
1260        return me->ReleaseAnimation(p);
1261    }
1262    static BASE_NS::shared_ptr<CORE_NS::IInterface> relnode(SceneImpl* me, const BASE_NS::string_view p)
1263    {
1264        return me->ReleaseNode(p);
1265    }
1266    template<typename Type>
1267    bool CacheNode(const char* const tname, const SCENE_NS::INode::Ptr& node,
1268        BASE_NS::unordered_map<BASE_NS::string, typename Type::WeakPtr>& cache, fun func)
1269    {
1270        if (auto typed = interface_pointer_cast<Type>(node)) {
1271            BASE_NS::string uri;
1272            bool found = false;
1273
1274            if (node->GetInterface(SCENE_NS::ICamera::UID)) {
1275                // handle camera naming..
1276                uri = node->Path()->GetValue();
1277                uri.append(node->Name()->GetValue());
1278
1279            } else {
1280                auto ecsObject = interface_pointer_cast<SCENE_NS::IEcsObject>(typed);
1281                uri = sceneHolder_->GetResourceId(ecsObject->GetEntity());
1282                if (uri.empty()) {
1283                    uri = node->Name()->GetValue() + ":" + BASE_NS::to_hex(ecsObject->GetEntity().id);
1284                }
1285            }
1286
1287            for (auto&& ite : cache) {
1288                if (ite.second.lock() == typed) {
1289                    if (uri != ite.first) {
1290                        cache[uri] = interface_pointer_cast<Type>(func(this, ite.first));
1291                        SCENE_PLUGIN_VERBOSE_LOG("Updating cached reference of %s: %s", tname, uri.c_str());
1292                    }
1293                    found = true;
1294                    break; // reference is valid so return true regardless if the node was moved
1295                }
1296            }
1297
1298            if (!found) {
1299                SCENE_PLUGIN_VERBOSE_LOG("Caching reference of %s: %s", tname, uri.c_str());
1300                cache[uri] = typed;
1301            }
1302            return true;
1303        }
1304        return false;
1305    }
1306    void UpdateCachedReference(const SCENE_NS::INode::Ptr& node) override
1307    {
1308        if (node) {
1309            if (CacheNode<SCENE_NS::IMaterial>("material", node, materials_, relmat) ||
1310                CacheNode<SCENE_NS::IMesh>("mesh", node, meshes_, relmesh) ||
1311                CacheNode<META_NS::IAnimation>("animation", node, animations_, relanim)) {
1312                // completed. (meshes, materials and animations are not added to node list)
1313                return;
1314            }
1315            if (CacheNode<SCENE_NS::ICamera>("camera", node, cameras_, relnode)) {
1316                // camera cache updated.
1317                auto* obj = interface_cast<SCENE_NS::IEcsObject>(node);
1318                if (obj) {
1319                    auto entity = obj->GetEntity();
1320                    if (CORE_NS::EntityUtil::IsValid(entity)) {
1321                        sceneHolder_->AddCamera(entity);
1322                    } else {
1323                        CORE_LOG_V("camera has no entity id yet");
1324                    }
1325                } else {
1326                    CORE_LOG_V("camera has no IEcsObject");
1327                }
1328            }
1329            // update node cache.
1330
1331            bool found = false;
1332            BASE_NS::string uri = node->Path()->GetValue();
1333            uri.append(node->Name()->GetValue());
1334
1335            for (auto&& ite : nodes_) {
1336                if (ite.second == node) {
1337                    if (uri != ite.first) {
1338                        nodes_[uri] = ReleaseNode(ite.first);
1339                    }
1340                    found = true;
1341                    break; // reference is valid so return true regardless if the node was moved
1342                }
1343            }
1344
1345            if (!found) {
1346                nodes_[uri] = node;
1347            }
1348        }
1349    }
1350
1351    // Release Node Reference from cache
1352    SCENE_NS::INode::Ptr ReleaseNode(const BASE_NS::string_view name) override
1353    {
1354        SCENE_NS::INode::Ptr ret {};
1355        if (auto ite = nodes_.extract(BASE_NS::string(name)); !ite.empty()) {
1356            ret = BASE_NS::move(ite.mapped());
1357            if (auto privateIntf = interface_cast<INodeEcsInterfacePrivate>(ret)) {
1358                privateIntf->ClaimOwnershipOfEntity(true);
1359            }
1360            RemoveNodeFromCurrentContainer(ret);
1361            if (auto cam = interface_pointer_cast<SCENE_NS::ICamera>(ret)) {
1362                ReleaseCamera(name);
1363            }
1364        }
1365        return ret;
1366    }
1367    void ReleaseNode(const SCENE_NS::INode::Ptr& node) override
1368    {
1369        if (node) {
1370            ReleaseNode(node->Path()->GetValue() + node->Name()->GetValue());
1371        }
1372    }
1373
1374    SCENE_NS::IMaterial::Ptr ReleaseMaterial(const BASE_NS::string_view name) override
1375    {
1376        SCENE_NS::IMaterial::Ptr ret {};
1377        if (auto ite = materials_.find(name); ite != materials_.end()) {
1378            ret = ite->second.lock();
1379            materials_.erase(ite);
1380        }
1381        return ret;
1382    }
1383
1384    SCENE_NS::IMesh::Ptr ReleaseMesh(const BASE_NS::string_view name) override
1385    {
1386        SCENE_NS::IMesh::Ptr ret {};
1387        if (auto ite = meshes_.find(name); ite != meshes_.end()) {
1388            ret = ite->second.lock();
1389            meshes_.erase(ite);
1390        }
1391
1392        return ret;
1393    }
1394
1395    META_NS::IAnimation::Ptr ReleaseAnimation(const BASE_NS::string_view name) override
1396    {
1397        META_NS::IAnimation::Ptr ret {};
1398        if (auto ite = animations_.find(name); ite != animations_.end()) {
1399            ret = ite->second.lock();
1400            animations_.erase(ite);
1401        }
1402        return ret;
1403    }
1404
1405    SCENE_NS::ICamera::Ptr ReleaseCamera(const BASE_NS::string_view name)
1406    {
1407        SCENE_NS::ICamera::Ptr ret {};
1408        if (auto ite = cameras_.find(name); ite != cameras_.end()) {
1409            ret = ite->second.lock();
1410            // make sure the rendertarget/bitmap is also released.
1411            SetBitmap(nullptr, ret);
1412            cameras_.erase(ite);
1413        }
1414        return ret;
1415    }
1416
1417    SCENE_NS::IMesh::Ptr CreateMeshFromArraysI16(
1418        const BASE_NS::string_view name, SCENE_NS::MeshGeometryArrayPtr<uint16_t> arrays) override
1419    {
1420        return CreateMeshFromArrays<uint16_t>(name, arrays, RENDER_NS::IndexType::CORE_INDEX_TYPE_UINT16);
1421    }
1422
1423    SCENE_NS::IMesh::Ptr CreateMeshFromArraysI32(
1424        const BASE_NS::string_view name, SCENE_NS::MeshGeometryArrayPtr<uint32_t> arrays) override
1425    {
1426        return CreateMeshFromArrays<uint32_t>(name, arrays, RENDER_NS::IndexType::CORE_INDEX_TYPE_UINT32);
1427    }
1428
1429    template<typename IndicesType>
1430    SCENE_NS::IMesh::Ptr CreateMeshFromArrays(const BASE_NS::string_view name,
1431        SCENE_NS::MeshGeometryArrayPtr<IndicesType> arrays, RENDER_NS::IndexType indexType)
1432    {
1433        auto nameString = BASE_NS::shared_ptr(new BASE_NS::string(name.data(), name.size()));
1434
1435        AddEngineTask(MakeTask(
1436                          [arrays, nameString, indexType](auto sceneHolder) {
1437                              auto meshEntity = sceneHolder->template CreateMeshFromArrays<IndicesType>(
1438                                  *nameString, arrays, indexType);
1439                              if (!sceneHolder->IsAsync()) {
1440                                  *nameString = sceneHolder->GetResourceId(meshEntity);
1441                              }
1442
1443                              return false;
1444                          },
1445                          sceneHolder_),
1446            false);
1447        return GetMesh(*nameString);
1448    }
1449
1450    void InstantiateMaterialProxies() override
1451    {
1452        AddEngineTask(MakeTask(
1453                          [me = BASE_NS::weak_ptr(GetSelf<SCENE_NS::IScene>())](auto sceneHolder) {
1454                              auto ids = sceneHolder->ListMaterialNames();
1455                              sceneHolder->QueueApplicationTask(MakeTask(
1456                                                                    [ids](auto self) {
1457                                                                        for (auto& id : *ids) {
1458                                                                            self->GetMaterial(id);
1459                                                                        }
1460                                                                        return false;
1461                                                                    },
1462                                                                    me),
1463                                  false);
1464                              return false;
1465                          },
1466                          sceneHolder_),
1467            false);
1468    }
1469
1470    void InstantiateMeshProxies() override
1471    {
1472        AddEngineTask(MakeTask(
1473                          [me = BASE_NS::weak_ptr(GetSelf<SCENE_NS::IScene>())](auto sceneHolder) {
1474                              auto ids = sceneHolder->ListMeshNames();
1475                              sceneHolder->QueueApplicationTask(MakeTask(
1476                                                                    [ids](auto self) {
1477                                                                        for (auto& id : *ids) {
1478                                                                            self->GetMesh(id);
1479                                                                        }
1480
1481                                                                        return false;
1482                                                                    },
1483                                                                    me),
1484                                  false);
1485
1486                              return false;
1487                          },
1488                          sceneHolder_),
1489            false);
1490    }
1491
1492public:
1493    ~SceneImpl() override
1494    {
1495        allAnims_.clear();
1496        if (sceneHolder_) {
1497            DetachScene();
1498        }
1499
1500        cameraNodePtr_.reset();
1501        rootNodePtr_.reset();
1502
1503        UnsubscribeFromPropertyChanges();
1504
1505        if (sceneHolder_) {
1506            sceneHolder_->Uninitialize();
1507            sceneHolder_.reset();
1508        }
1509    }
1510
1511    void SetEcsInitializationCallback(IPrepareSceneForInitialization::WeakPtr callback) override
1512    {
1513        if (sceneHolder_) {
1514            sceneHolder_->SetEcsInitializationCallback(callback);
1515        } else {
1516            CORE_LOG_W("%s: sceneholder does not exist, cannot set callback", __func__);
1517        }
1518    }
1519
1520    SCENE_NS::IPickingResult::Ptr GetWorldAABB(const BASE_NS::Math::Mat4X4& world, const BASE_NS::Math::Vec3& aabbMin,
1521        const BASE_NS::Math::Vec3& aabbMax) override
1522    {
1523        auto ret = META_NS::GetObjectRegistry().Create<SCENE_NS::IPickingResult>(SCENE_NS::ClassId::PendingVec3Request);
1524        if (ret) {
1525            AddEngineTask(
1526                META_NS::MakeCallback<META_NS::ITaskQueueTask>([w = world, min = aabbMin, max = aabbMax,
1527                                                                   weakRet = BASE_NS::weak_ptr(ret),
1528                                                                   weakSh = BASE_NS::weak_ptr(sceneHolder_)]() {
1529                    if (auto sh = weakSh.lock()) {
1530                        if (auto ret = weakRet.lock()) {
1531                            if (sh->GetWorldAABB(ret, w, min, max)) {
1532                                sh->QueueApplicationTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>([weakRet]() {
1533                                    if (auto writable =
1534                                            interface_pointer_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(
1535                                                weakRet)) {
1536                                        writable->MarkReady();
1537                                    }
1538                                    return false;
1539                                }),
1540                                    false);
1541                            }
1542                        }
1543                    }
1544                    return false;
1545                }),
1546                false);
1547            return ret;
1548        }
1549        return SCENE_NS::IPickingResult::Ptr();
1550    }
1551
1552    SCENE_NS::IRayCastResult::Ptr RayCast(
1553        const BASE_NS::Math::Vec3& start, const BASE_NS::Math::Vec3& direction) override
1554    {
1555        auto ret =
1556            META_NS::GetObjectRegistry().Create<SCENE_NS::IRayCastResult>(SCENE_NS::ClassId::PendingDistanceRequest);
1557        if (ret) {
1558            AddEngineTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>([origin = start, dir = direction,
1559                                                                             weakRet = BASE_NS::weak_ptr(ret),
1560                                                                             weakSh = BASE_NS::weak_ptr(sceneHolder_),
1561                                                                             weakSelf = BASE_NS::weak_ptr(
1562                                                                                 GetSelf<SCENE_NS::IScene>())]() {
1563                if (auto sh = weakSh.lock()) {
1564                    if (auto ret = weakRet.lock()) {
1565                        if (sh->RayCast(ret, origin, dir)) {
1566                            sh->QueueApplicationTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>([weakRet,
1567                                                                                                        weakSelf]() {
1568                                if (auto writable =
1569                                        interface_pointer_cast<SCENE_NS::IPendingRequestData<SCENE_NS::NodeDistance>>(
1570                                            weakRet)) {
1571                                    if (auto self = weakSelf.lock()) {
1572                                        // resolve proxy nodes
1573                                        for (size_t ii = writable->MetaData().size(); ii > 0;) {
1574                                            --ii;
1575                                            writable->MutableData().at(ii).node =
1576                                                self->GetNode(writable->MetaData().at(ii));
1577                                        }
1578                                        writable->MarkReady();
1579                                    }
1580                                }
1581                                return false;
1582                            }),
1583                                false);
1584                        }
1585                    }
1586                }
1587                return false;
1588            }),
1589                false);
1590            return ret;
1591        }
1592        return SCENE_NS::IRayCastResult::Ptr();
1593    }
1594
1595    SCENE_NS::IRayCastResult::Ptr RayCast(
1596        const BASE_NS::Math::Vec3& start, const BASE_NS::Math::Vec3& direction, uint64_t layerMask) override
1597    {
1598        auto ret =
1599            META_NS::GetObjectRegistry().Create<SCENE_NS::IRayCastResult>(SCENE_NS::ClassId::PendingDistanceRequest);
1600        if (ret) {
1601            AddEngineTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>([origin = start, dir = direction, layerMask,
1602                                                                             weakRet = BASE_NS::weak_ptr(ret),
1603                                                                             weakSh = BASE_NS::weak_ptr(sceneHolder_),
1604                                                                             weakSelf = BASE_NS::weak_ptr(
1605                                                                                 GetSelf<SCENE_NS::IScene>())]() {
1606                if (auto sh = weakSh.lock()) {
1607                    if (auto ret = weakRet.lock()) {
1608                        if (sh->RayCast(ret, origin, dir, layerMask)) {
1609                            sh->QueueApplicationTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>([weakRet,
1610                                                                                                        weakSelf]() {
1611                                if (auto writable =
1612                                        interface_pointer_cast<SCENE_NS::IPendingRequestData<SCENE_NS::NodeDistance>>(
1613                                            weakRet)) {
1614                                    if (auto self = weakSelf.lock()) {
1615                                        // resolve proxy nodes
1616                                        for (size_t ii = writable->MetaData().size(); ii > 0;) {
1617                                            --ii;
1618                                            writable->MutableData().at(ii).node =
1619                                                self->GetNode(writable->MetaData().at(ii));
1620                                        }
1621                                        writable->MarkReady();
1622                                    }
1623                                }
1624                                return false;
1625                            }),
1626                                false);
1627                        }
1628                    }
1629                }
1630                return false;
1631            }),
1632                false);
1633            return ret;
1634        }
1635        return SCENE_NS::IRayCastResult::Ptr();
1636    }
1637    BASE_NS::vector<CORE_NS::Entity> RenderCameras() override
1638    {
1639        if (sceneHolder_) {
1640            return sceneHolder_->RenderCameras();
1641        }
1642        return {};
1643    }
1644    void ActivateCamera(const SCENE_NS::ICamera::Ptr& camera) override
1645    {
1646        if (auto e = interface_pointer_cast<SCENE_NS::IEcsObject>(camera)) {
1647            auto ent = e->GetEntity();
1648            sceneHolder_->ActivateCamera(ent);
1649        }
1650    }
1651
1652    void DeactivateCamera(const SCENE_NS::ICamera::Ptr& camera) override
1653    {
1654        if (!camera) {
1655            return;
1656        }
1657
1658        if (auto e = interface_pointer_cast<SCENE_NS::IEcsObject>(camera)) {
1659            sceneHolder_->DeactivateCamera(e->GetEntity());
1660        }
1661    }
1662    bool IsCameraActive(const SCENE_NS::ICamera::Ptr& camera) override
1663    {
1664        if (!camera) {
1665            return false;
1666        }
1667
1668        if (auto e = interface_pointer_cast<SCENE_NS::IEcsObject>(camera)) {
1669            return sceneHolder_->IsCameraActive(e->GetEntity());
1670        }
1671        return false;
1672    }
1673
1674private:
1675    void SubscribeToPropertyChanges()
1676    {
1677        if (RootNode()) {
1678            rootNodeChangedToken_ = RootNode()->OnChanged()->AddHandler(
1679                META_NS::MakeCallback<META_NS::IOnChanged>(this, &SceneImpl::OnRootNodeChanged));
1680        }
1681
1682        if (Uri()) {
1683            uriHandlerToken_ = Uri()->OnChanged()->AddHandler(
1684                META_NS::MakeCallback<META_NS::IOnChanged>([this]() { this->Load(Uri()->GetValue()); }));
1685        }
1686
1687        // Start listening changes of the scene controller properties. These may go to different place some day
1688        if (SystemGraphUri()) {
1689            systemGraphUriHandlerToken_ = SystemGraphUri()->OnChanged()->AddHandler(
1690                META_NS::MakeCallback<META_NS::IOnChanged>(this, &SceneImpl::onSystemGraphUriChanged));
1691        }
1692    }
1693
1694    void UnsubscribeFromPropertyChanges()
1695    {
1696        if (Uri()) {
1697            Uri()->OnChanged()->RemoveHandler(uriHandlerToken_);
1698            uriHandlerToken_ = {};
1699        }
1700
1701        if (RootNode()) {
1702            RootNode()->OnChanged()->RemoveHandler(rootNodeChangedToken_);
1703            rootNodeChangedToken_ = {};
1704        }
1705
1706        if (SystemGraphUri()) {
1707            SystemGraphUri()->OnChanged()->RemoveHandler(systemGraphUriHandlerToken_);
1708            systemGraphUriHandlerToken_ = {};
1709        }
1710
1711        if (Asynchronous()) {
1712            Asynchronous()->OnChanged()->RemoveHandler(asyncChangedToken_);
1713            asyncChangedToken_ = {};
1714        }
1715    }
1716
1717    void OnRootNodeChanged()
1718    {
1719        auto contentObject = interface_pointer_cast<META_NS::IObject>(RootNode()->GetValue());
1720        contentImpl_->SetContent(contentObject);
1721
1722        META_NS::HierarchyChangeModeValue changeMode;
1723        changeMode.Set(META_NS::HierarchyChangeMode::NOTIFY_CONTAINER);
1724        hierarchyController_->SetTarget(contentObject, changeMode);
1725    }
1726
1727    META_NS::IEvent::Token systemGraphUriHandlerToken_ {};
1728    META_NS::IEvent::Token renderSizeHandlerToken_ {};
1729    META_NS::IEvent::Token cameraHandlerToken_ {};
1730    META_NS::IEvent::Token uriHandlerToken_ {};
1731    META_NS::IEvent::Token rootNodeChangedToken_ {};
1732    META_NS::IEvent::Token renderModeChangedToken_ {};
1733    META_NS::IEvent::Token asyncChangedToken_ {};
1734
1735    SceneHolder::Ptr sceneHolder_;
1736
1737    BASE_NS::string rootNodeId_;
1738    BASE_NS::unordered_map<BASE_NS::string, SCENE_NS::INode::Ptr> nodes_;
1739    BASE_NS::unordered_map<BASE_NS::string, SCENE_NS::ICamera::WeakPtr> cameras_;
1740    BASE_NS::unordered_map<BASE_NS::string, SCENE_NS::IMesh::WeakPtr> meshes_;
1741    BASE_NS::unordered_map<BASE_NS::string, SCENE_NS::IMaterial::WeakPtr> materials_;
1742    BASE_NS::unordered_map<BASE_NS::string, META_NS::IAnimation::WeakPtr> animations_;
1743
1744    BASE_NS::unordered_map<BASE_NS::string, SCENE_NS::IMaterial::WeakPtr> uriMaterials_;
1745
1746    uint64_t instanceNumber_ { 0 };
1747
1748    // Preserve property instances while scene / ecs is invalid
1749    SCENE_NS::INode::Ptr rootNodePtr_ {};
1750    SCENE_NS::ICamera::Ptr cameraNodePtr_ {};
1751    META_NS::IContent::Ptr contentImpl_ {};
1752
1753    uint64_t defaultCameraHandle_ { 0 };
1754
1755    // We need to add a node onto a container in bit awkward position (without exposing it to a public api)
1756    // Store it here.
1757    SCENE_NS::INode::Ptr currentParent_ {};
1758    META_NS::IObjectHierarchyObserver::Ptr hierarchyController_;
1759
1760    META_NS::IAnimationController::Ptr animationController_;
1761};
1762
1763BASE_NS::vector<BASE_NS::weak_ptr<META_NS::IAnimation>> SceneImpl::GetAnimations() const
1764{
1765    return animationController_->GetAnimations();
1766}
1767BASE_NS::vector<BASE_NS::weak_ptr<META_NS::IAnimation>> SceneImpl::GetRunning() const
1768{
1769    return animationController_->GetRunning();
1770}
1771bool SceneImpl::AddAnimation(const BASE_NS::shared_ptr<META_NS::IAnimation>& animation)
1772{
1773    return animationController_->AddAnimation(animation);
1774}
1775bool SceneImpl::RemoveAnimation(const BASE_NS::shared_ptr<META_NS::IAnimation>& animation)
1776{
1777    return animationController_->RemoveAnimation(animation);
1778}
1779void SceneImpl::Clear()
1780{
1781    animationController_->Clear();
1782}
1783META_NS::IAnimationController::StepInfo SceneImpl::Step(const META_NS::IClock::ConstPtr& clock)
1784{
1785    return animationController_->Step(clock);
1786}
1787
1788} // namespace
1789SCENE_BEGIN_NAMESPACE()
1790void RegisterSceneImpl()
1791{
1792    META_NS::GetObjectRegistry().RegisterObjectType<SceneImpl>();
1793}
1794void UnregisterSceneImpl()
1795{
1796    META_NS::GetObjectRegistry().UnregisterObjectType<SceneImpl>();
1797}
1798SCENE_END_NAMESPACE()
1799