< Summary

Class:DCL.SceneController
Assembly:DCL.Runtime
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/WorldRuntime/SceneController.cs
Covered lines:204
Uncovered lines:192
Coverable lines:396
Total lines:967
Line coverage:51.5% (204 of 396)
Covered branches:0
Total branches:0
Covered methods:33
Total methods:53
Method coverage:62.2% (33 of 53)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SceneController(...)0%110100%
Initialize()0%110100%
OnDebugModeSet(...)0%4.123050%
Dispose()0%44093.75%
Update()0%440100%
LateUpdate()0%220100%
ProcessMessage(...)0%10.6610081.25%
ProcessMessage(...)0%1946.7756015.52%
ParseQuery(...)0%12300%
SendSceneMessage(...)0%6200%
Decode(...)0%20400%
EnqueueChunk(...)0%6200%
EnqueueSceneMessage(...)0%110100%
SendSceneReady(...)0%2.022083.33%
SetPositionDirty(...)0%4.054085.71%
SortScenesByDistance()0%440100%
OnCurrentSceneNumberChange(...)0%440100%
LoadParcelScenesExecute(...)0%330100%
LoadUnityParcelScene()0%23.6518074.07%
RequestSceneContentCategory()0%232.071709.38%
UpdateParcelScenesExecute(...)0%20400%
UpdateParcelScenesExecute(...)0%42600%
UnloadScene(...)0%220100%
UnloadParcelSceneExecute(...)0%7.077088.89%
UnloadAllScenes(...)0%440100%
LoadParcelScenes(...)0%220100%
UpdateParcelScenes(...)0%6200%
UnloadAllScenesQueued()0%6200%
CreateGlobalScene(...)0%14.2910065%
IsolateScene(...)0%12300%
ReIntegrateIsolatedScene()0%6200%
ShouldForceAcceptPortableExperience(...)0%2100%
IsPortableExperienceConfirmedAndAccepted(...)0%2100%
IsPortableExperienceAlreadyConfirmed(...)0%2100%
IsPortableExperienceInWhiteList(...)0%30500%
OnAdultContentSettingChange(...)0%6.83025%
ReloadAdultScenesAsync()0%72800%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/WorldRuntime/SceneController.cs

#LineLine coverage
 1using DCL.Controllers;
 2using DCL.Helpers;
 3using DCL.Interface;
 4using DCL.Models;
 5using DCL.Configuration;
 6using System;
 7using System.Collections;
 8using System.Collections.Concurrent;
 9using System.Linq;
 10using System.Threading;
 11using Cysharp.Threading.Tasks;
 12using DCL.Components;
 13using DCL.Tasks;
 14using DCL.World.PortableExperiences;
 15using DCLServices.PlacesAPIService;
 16using DCLServices.PortableExperiences.Analytics;
 17using DCLServices.WorldsAPIService;
 18using MainScripts.DCL.Controllers.HotScenes;
 19using Newtonsoft.Json;
 20using System.Collections.Generic;
 21using System.Threading.Tasks;
 22using UnityEngine;
 23using Debug = UnityEngine.Debug;
 24
 25namespace DCL
 26{
 27    public class SceneController : ISceneController
 28    {
 29        private const bool VERBOSE = false;
 30        private const int SCENE_MESSAGES_PREWARM_COUNT = 100000;
 31        private const int REQUEST_PLACE_TIME_OUT = 10;
 32        private const string EMPTY_PARCEL_NAME = "Empty parcel";
 33
 34        private readonly IConfirmedExperiencesRepository confirmedExperiencesRepository;
 35
 236        private BaseVariable<Transform> isPexViewerInitialized => DataStore.i.experiencesViewer.isInitialized;
 37
 38        //TODO(Brian): Move to WorldRuntimePlugin later
 39        private Coroutine deferredDecodingCoroutine;
 40        private CancellationTokenSource tokenSource;
 41        private CancellationTokenSource requestPlaceCts;
 42        private CancellationTokenSource reloadAdultScenesCts;
 58343        private IMessagingControllersManager messagingControllersManager => Environment.i.messaging.manager;
 444        private BaseDictionary<string, (string name, string description, string icon)> disabledPortableExperiences => Da
 645        private BaseHashSet<string> portableExperienceIds => DataStore.i.world.portableExperienceIds;
 046        private BaseVariable<ExperiencesConfirmationData> pendingPortableExperienceToBeConfirmed => DataStore.i.world.po
 247        private IPortableExperiencesAnalyticsService portableExperiencesAnalytics => Environment.i.serviceLocator.Get<IP
 048        private IPlacesAPIService placesAPIService => Environment.i.serviceLocator.Get<IPlacesAPIService>();
 049        private IWorldsAPIService worldsAPIService => Environment.i.serviceLocator.Get<IWorldsAPIService>();
 3650        private bool isContentModerationFeatureEnabled => DataStore.i.featureFlags.flags.Get().IsFeatureEnabled("content
 51
 44052        public EntityIdHelper entityIdHelper { get; } = new EntityIdHelper();
 2467753        public bool enabled { get; set; } = true;
 54
 44055        public SceneController(IConfirmedExperiencesRepository confirmedExperiencesRepository)
 56        {
 44057            this.confirmedExperiencesRepository = confirmedExperiencesRepository;
 44058        }
 59
 60        public void Initialize()
 61        {
 44062            tokenSource = new CancellationTokenSource();
 44063            requestPlaceCts = requestPlaceCts.SafeRestart();
 44064            sceneSortDirty = true;
 44065            positionDirty = true;
 44066            lastSortFrame = 0;
 44067            enabled = true;
 68
 44069            DataStore.i.debugConfig.isDebugMode.OnChange += OnDebugModeSet;
 44070            DataStore.i.player.playerGridPosition.OnChange += SetPositionDirty;
 44071            CommonScriptableObjects.sceneNumber.OnChange += OnCurrentSceneNumberChange;
 44072            DataStore.i.contentModeration.adultContentSettingEnabled.OnChange += OnAdultContentSettingChange;
 73
 44074            Environment.i.platform.updateEventHandler.AddListener(IUpdateEventHandler.EventType.Update, Update);
 44075            Environment.i.platform.updateEventHandler.AddListener(IUpdateEventHandler.EventType.LateUpdate, LateUpdate);
 44076        }
 77
 78        private void OnDebugModeSet(bool current, bool previous)
 79        {
 3080            if (current == previous)
 081                return;
 82
 6083            if (current) { Environment.i.world.sceneBoundsChecker.SetFeedbackStyle(new SceneBoundsFeedbackStyle_RedBox()
 084            else { Environment.i.world.sceneBoundsChecker.SetFeedbackStyle(new SceneBoundsFeedbackStyle_Simple()); }
 085        }
 86
 87        public void Dispose()
 88        {
 44089            tokenSource?.Cancel();
 44090            tokenSource?.Dispose();
 44091            requestPlaceCts.SafeCancelAndDispose();
 44092            reloadAdultScenesCts.SafeCancelAndDispose();
 93
 44094            Environment.i.platform.updateEventHandler.RemoveListener(IUpdateEventHandler.EventType.Update, Update);
 44095            Environment.i.platform.updateEventHandler.RemoveListener(IUpdateEventHandler.EventType.LateUpdate, LateUpdat
 96
 44097            PoolManager.i.OnGet -= Environment.i.platform.physicsSyncController.MarkDirty;
 44098            PoolManager.i.OnGet -= Environment.i.platform.cullingController.objectsTracker.MarkDirty;
 99
 440100            DataStore.i.player.playerGridPosition.OnChange -= SetPositionDirty;
 440101            DataStore.i.debugConfig.isDebugMode.OnChange -= OnDebugModeSet;
 440102            DataStore.i.contentModeration.adultContentSettingEnabled.OnChange -= OnAdultContentSettingChange;
 103
 440104            CommonScriptableObjects.sceneNumber.OnChange -= OnCurrentSceneNumberChange;
 105
 440106            UnloadAllScenes(includePersistent: true);
 107
 440108            if (deferredDecodingCoroutine != null)
 0109                CoroutineStarter.Stop(deferredDecodingCoroutine);
 440110        }
 111
 112        public void Update()
 113        {
 11886114            if (!enabled)
 1303115                return;
 116
 10583117            if (lastSortFrame != Time.frameCount && sceneSortDirty)
 118            {
 444119                lastSortFrame = Time.frameCount;
 444120                sceneSortDirty = false;
 444121                SortScenesByDistance();
 122            }
 10583123        }
 124
 125        public void LateUpdate()
 126        {
 11886127            if (!enabled)
 1303128                return;
 129
 10583130            Environment.i.platform.physicsSyncController.Sync();
 10583131        }
 132
 133        //======================================================================
 134
 135#region MESSAGES_HANDLING
 136        //======================================================================
 137
 138#if UNITY_EDITOR
 139        public delegate void ProcessDelegate(int sceneNumber, string method);
 140
 141        public event ProcessDelegate OnMessageProcessInfoStart;
 142        public event ProcessDelegate OnMessageProcessInfoEnds;
 143#endif
 0144        public bool deferredMessagesDecoding { get; set; } = false;
 145
 440146        readonly ConcurrentQueue<string> chunksToDecode = new ConcurrentQueue<string>();
 440147        private readonly ConcurrentQueue<QueuedSceneMessage_Scene> messagesToProcess = new ConcurrentQueue<QueuedSceneMe
 148
 149        const float MAX_TIME_FOR_DECODE = 0.005f;
 150
 151        public bool ProcessMessage(QueuedSceneMessage_Scene msgObject, out CustomYieldInstruction yieldInstruction)
 152        {
 1153            int sceneNumber = msgObject.sceneNumber;
 1154            string method = msgObject.method;
 155
 1156            yieldInstruction = null;
 157
 158            IParcelScene scene;
 1159            DebugConfig debugConfig = DataStore.i.debugConfig;
 160
 1161            if (Environment.i.world.state.TryGetScene(sceneNumber, out scene))
 162            {
 163#if UNITY_EDITOR
 1164                if (debugConfig.soloScene && scene is GlobalScene && debugConfig.ignoreGlobalScenes) { return false; }
 165#endif
 1166                if (!scene.GetSceneTransform().gameObject.activeInHierarchy) { return true; }
 167
 168#if UNITY_EDITOR
 1169                OnMessageProcessInfoStart?.Invoke(sceneNumber, method);
 170#endif
 1171                ProfilingEvents.OnMessageProcessStart?.Invoke(method);
 172
 1173                ProcessMessage(scene as ParcelScene, method, msgObject.payload, out yieldInstruction);
 174
 1175                ProfilingEvents.OnMessageProcessEnds?.Invoke(method);
 176
 177#if UNITY_EDITOR
 1178                OnMessageProcessInfoEnds?.Invoke(sceneNumber, method);
 179#endif
 180
 1181                return true;
 182            }
 183
 0184            return false;
 185        }
 186
 187        private void ProcessMessage(ParcelScene scene, string method, object msgPayload, out CustomYieldInstruction yiel
 188        {
 1189            yieldInstruction = null;
 1190            IDelayedComponent delayedComponent = null;
 191
 192            try
 193            {
 1194                if (isContentModerationFeatureEnabled && method != MessagingTypes.INIT_DONE)
 195                {
 0196                    if (scene.contentCategory == SceneContentCategory.RESTRICTED ||
 197                        (scene.contentCategory == SceneContentCategory.ADULT && !DataStore.i.contentModeration.adultCont
 0198                        return;
 199                }
 200
 201                switch (method)
 202                {
 203                    case MessagingTypes.ENTITY_CREATE:
 204                    {
 0205                        if (msgPayload is Protocol.CreateEntity payload)
 0206                            scene.CreateEntity(entityIdHelper.EntityFromLegacyEntityString(payload.entityId));
 207
 0208                        break;
 209                    }
 210                    case MessagingTypes.ENTITY_REPARENT:
 211                    {
 0212                        if (msgPayload is Protocol.SetEntityParent payload)
 0213                            scene.SetEntityParent(entityIdHelper.EntityFromLegacyEntityString(payload.entityId), entityI
 214
 0215                        break;
 216                    }
 217                    case MessagingTypes.PB_SHARED_COMPONENT_UPDATE:
 218                    {
 0219                        if (msgPayload is Decentraland.Sdk.Ecs6.ComponentUpdatedBody payload)
 220                        {
 0221                            if (payload.ComponentData != null) { delayedComponent = scene.componentsManagerLegacy.SceneS
 222                        }
 223
 0224                        break;
 225                    }
 226                    case MessagingTypes.PB_ENTITY_COMPONENT_CREATE_OR_UPDATE:
 227                    {
 0228                        if (msgPayload is Decentraland.Sdk.Ecs6.UpdateEntityComponentBody payload)
 229                        {
 0230                            if (payload.ComponentData != null)
 231                            {
 0232                                delayedComponent = scene.componentsManagerLegacy.EntityComponentCreateOrUpdate(entityIdH
 233                                    (CLASS_ID_COMPONENT)payload.ClassId, payload.ComponentData) as IDelayedComponent;
 234                            }
 235                        }
 236
 0237                        break;
 238                    }
 239                    case MessagingTypes.ENTITY_COMPONENT_DESTROY:
 240                    {
 0241                        if (msgPayload is Protocol.EntityComponentDestroy payload)
 0242                            scene.componentsManagerLegacy.EntityComponentRemove(entityIdHelper.EntityFromLegacyEntityStr
 243
 0244                        break;
 245                    }
 246                    case MessagingTypes.SHARED_COMPONENT_ATTACH:
 247                    {
 0248                        if (msgPayload is Protocol.SharedComponentAttach payload)
 0249                            scene.componentsManagerLegacy.SceneSharedComponentAttach(entityIdHelper.EntityFromLegacyEnti
 250
 0251                        break;
 252                    }
 253                    case MessagingTypes.SHARED_COMPONENT_CREATE:
 254                    {
 0255                        if (msgPayload is Protocol.SharedComponentCreate payload)
 0256                            scene.componentsManagerLegacy.SceneSharedComponentCreate(payload.id, payload.classId);
 257
 0258                        break;
 259                    }
 260                    case MessagingTypes.SHARED_COMPONENT_DISPOSE:
 261                    {
 0262                        if (msgPayload is Protocol.SharedComponentDispose payload)
 0263                            scene.componentsManagerLegacy.SceneSharedComponentDispose(payload.id);
 264
 0265                        break;
 266                    }
 267                    case MessagingTypes.ENTITY_DESTROY:
 268                    {
 0269                        if (msgPayload is Protocol.RemoveEntity payload)
 0270                            scene.RemoveEntity(entityIdHelper.EntityFromLegacyEntityString(payload.entityId));
 271
 0272                        break;
 273                    }
 274                    case MessagingTypes.INIT_DONE:
 275                    {
 1276                        if (!scene.IsInitMessageDone())
 1277                            scene.sceneLifecycleHandler.SetInitMessagesDone();
 278
 1279                        break;
 280                    }
 281                    case MessagingTypes.QUERY:
 282                    {
 0283                        if (msgPayload is QueryMessage queryMessage)
 0284                            ParseQuery(queryMessage.payload, scene.sceneData.sceneNumber);
 285
 0286                        break;
 287                    }
 288                    case MessagingTypes.OPEN_EXTERNAL_URL:
 289                    {
 0290                        if (msgPayload is Protocol.OpenExternalUrl payload)
 291                        {
 292                            // SDK6 permission check for PX
 0293                            if (scene.isPortableExperience && !scene.sceneData.requiredPermissions.Contains(ScenePermiss
 294                            {
 0295                                Debug.LogError($"PX requires permission {ScenePermissionNames.OPEN_EXTERNAL_LINK}");
 0296                                return;
 297                            }
 298
 0299                            OnOpenExternalUrlRequest?.Invoke(scene, payload.url);
 300                        }
 301
 0302                        break;
 303                    }
 304                    case MessagingTypes.OPEN_NFT_DIALOG:
 305                    {
 306                        // TODO: update protocol
 0307                        if (msgPayload is Protocol.OpenNftDialog payload)
 0308                            DataStore.i.common.onOpenNFTPrompt.Set(new NFTPromptModel("ethereum", payload.contactAddress
 309
 0310                        break;
 311                    }
 312
 313                    default:
 0314                        Debug.LogError($"Unknown method {method}");
 315                        break;
 316                }
 1317            }
 318            catch (Exception e)
 319            {
 0320                Debug.LogException(e);
 0321                Debug.LogError($"Scene message error. scene: {scene.sceneData.sceneNumber} method: {method} payload: {Js
 0322            }
 323
 1324            if (delayedComponent != null)
 325            {
 0326                if (delayedComponent.isRoutineRunning) { yieldInstruction = delayedComponent.yieldInstruction; }
 327            }
 1328        }
 329
 330        public void ParseQuery(object payload, int sceneNumber)
 331        {
 0332            if (!Environment.i.world.state.TryGetScene(sceneNumber, out var scene)) return;
 333
 0334            if (!(payload is RaycastQuery raycastQuery))
 0335                return;
 336
 0337            Vector3 worldOrigin = raycastQuery.ray.origin + Utils.GridToWorldPosition(scene.sceneData.basePosition.x, sc
 338
 0339            raycastQuery.ray.unityOrigin = PositionUtils.WorldToUnityPosition(worldOrigin);
 0340            raycastQuery.sceneNumber = sceneNumber;
 0341            PhysicsCast.i.Query(raycastQuery, entityIdHelper);
 0342        }
 343
 344        public void SendSceneMessage(string chunk)
 345        {
 0346            var renderer = CommonScriptableObjects.rendererState.Get();
 347
 0348            if (!renderer) { EnqueueChunk(chunk); }
 0349            else { chunksToDecode.Enqueue(chunk); }
 0350        }
 351
 352        private QueuedSceneMessage_Scene Decode(string payload, QueuedSceneMessage_Scene queuedMessage)
 353        {
 0354            ProfilingEvents.OnMessageDecodeStart?.Invoke("Misc");
 355
 0356            if (!MessageDecoder.DecodePayloadChunk(payload,
 357                    out int sceneNumber,
 358                    out string message,
 359                    out string messageTag,
 0360                    out PB_SendSceneMessage sendSceneMessage)) { return null; }
 361
 0362            MessageDecoder.DecodeSceneMessage(sceneNumber, message, messageTag, sendSceneMessage, ref queuedMessage);
 363
 0364            ProfilingEvents.OnMessageDecodeEnds?.Invoke("Misc");
 365
 0366            return queuedMessage;
 367        }
 368
 369        private void EnqueueChunk(string chunk)
 370        {
 0371            string[] payloads = chunk.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
 0372            var count = payloads.Length;
 373
 0374            for (int i = 0; i < count; i++)
 375            {
 0376                EnqueueSceneMessage(Decode(payloads[i], new QueuedSceneMessage_Scene()));
 377            }
 0378        }
 379
 380        public void EnqueueSceneMessage(QueuedSceneMessage_Scene message)
 381        {
 1382            bool isGlobalScene = WorldStateUtils.IsGlobalScene(message.sceneNumber);
 1383            messagingControllersManager.AddControllerIfNotExists(this, message.sceneNumber);
 1384            messagingControllersManager.Enqueue(isGlobalScene, message);
 1385        }
 386
 387        //======================================================================
 388#endregion
 389
 390        //======================================================================
 391
 392        //======================================================================
 393
 394#region SCENES_MANAGEMENT
 395        //======================================================================
 396        public event Action<int> OnReadyScene;
 397
 398        public void SendSceneReady(int sceneNumber)
 399        {
 241400            messagingControllersManager.SetSceneReady(sceneNumber);
 401
 241402            WebInterface.ReportControlEvent(new WebInterface.SceneReady(sceneNumber));
 241403            WebInterface.ReportCameraChanged(CommonScriptableObjects.cameraMode.Get(), sceneNumber);
 404
 241405            Environment.i.world.blockersController.SetupWorldBlockers();
 406
 241407            OnReadyScene?.Invoke(sceneNumber);
 0408        }
 409
 410        private void SetPositionDirty(Vector2Int gridPosition, Vector2Int previous)
 411        {
 11412            positionDirty = gridPosition.x != currentGridSceneCoordinate.x || gridPosition.y != currentGridSceneCoordina
 413
 11414            if (positionDirty)
 415            {
 11416                sceneSortDirty = true;
 11417                currentGridSceneCoordinate = gridPosition;
 418
 419                // Since the first position for the character is not sent from Kernel until just-before calling
 420                // the rendering activation from Kernel, we need to sort the scenes to get the current scene id
 421                // to lock the rendering accordingly...
 11422                if (!CommonScriptableObjects.rendererState.Get()) { SortScenesByDistance(); }
 423            }
 11424        }
 425
 426        public void SortScenesByDistance()
 427        {
 444428            IWorldState worldState = Environment.i.world.state;
 429
 444430            worldState.SortScenesByDistance(currentGridSceneCoordinate);
 431
 444432            int currentSceneNumber = worldState.GetCurrentSceneNumber();
 433
 444434            if (!DataStore.i.debugConfig.isDebugMode.Get() && currentSceneNumber <= 0)
 435            {
 436                // When we don't know the current scene yet, we must lock the rendering from enabling until it is set
 431437                CommonScriptableObjects.rendererState.AddLock(this);
 438            }
 439            else
 440            {
 441                // 1. Set current scene id
 13442                CommonScriptableObjects.sceneNumber.Set(currentSceneNumber);
 443
 444                // 2. Attempt to remove SceneController's lock on rendering
 13445                CommonScriptableObjects.rendererState.RemoveLock(this);
 446            }
 447
 444448            OnSortScenes?.Invoke();
 444449        }
 450
 451        private void OnCurrentSceneNumberChange(int newSceneNumber, int previousSceneNumber)
 452        {
 72453            if (Environment.i.world.state.TryGetScene(newSceneNumber, out IParcelScene newCurrentScene)
 454                && !(newCurrentScene as ParcelScene).sceneLifecycleHandler.isReady)
 455            {
 6456                CommonScriptableObjects.rendererState.AddLock(newCurrentScene);
 457
 6458                (newCurrentScene as ParcelScene).sceneLifecycleHandler.OnSceneReady += (readyScene) => { CommonScriptabl
 459            }
 72460        }
 461
 462        public void LoadParcelScenesExecute(string scenePayload)
 463        {
 464            LoadParcelScenesMessage.UnityParcelScene sceneToLoad;
 465
 28466            ProfilingEvents.OnMessageDecodeStart?.Invoke(MessagingTypes.SCENE_LOAD);
 28467            sceneToLoad = Utils.SafeFromJson<LoadParcelScenesMessage.UnityParcelScene>(scenePayload);
 28468            ProfilingEvents.OnMessageDecodeEnds?.Invoke(MessagingTypes.SCENE_LOAD);
 469
 28470            LoadUnityParcelScene(sceneToLoad).Forget();
 28471        }
 472
 473        public async UniTaskVoid LoadUnityParcelScene(LoadParcelScenesMessage.UnityParcelScene sceneToLoad)
 474        {
 34475            if (sceneToLoad == null || sceneToLoad.sceneNumber <= 0)
 0476                return;
 477
 478            if (VERBOSE)
 479                Debug.Log($"{Time.frameCount}: Trying to load scene: id: {sceneToLoad.id}; number: {sceneToLoad.sceneNum
 480
 34481            DebugConfig debugConfig = DataStore.i.debugConfig;
 482#if UNITY_EDITOR
 34483            if (debugConfig.soloScene && sceneToLoad.basePosition.ToString() != debugConfig.soloSceneCoords.ToString())
 484            {
 0485                SendSceneReady(sceneToLoad.sceneNumber);
 486
 0487                return;
 488            }
 489#endif
 490
 34491            ProfilingEvents.OnMessageProcessStart?.Invoke(MessagingTypes.SCENE_LOAD);
 492
 34493            IWorldState worldState = Environment.i.world.state;
 494
 34495            if (!worldState.ContainsScene(sceneToLoad.sceneNumber))
 496            {
 34497                var newGameObject = new GameObject("New Scene");
 498
 34499                var newScene = newGameObject.AddComponent<ParcelScene>();
 34500                await newScene.SetData(sceneToLoad);
 34501                await RequestSceneContentCategory(newScene);
 502
 62503                if (debugConfig.isDebugMode.Get()) { newScene.InitializeDebugPlane(); }
 504
 34505                worldState.AddScene(newScene);
 506
 34507                sceneSortDirty = true;
 508
 34509                if (newScene.contentCategory != SceneContentCategory.RESTRICTED &&
 510                    (newScene.contentCategory != SceneContentCategory.ADULT || (newScene.contentCategory == SceneContent
 34511                    OnNewSceneAdded?.Invoke(newScene);
 512
 34513                messagingControllersManager.AddControllerIfNotExists(this, newScene.sceneData.sceneNumber);
 514
 515                if (VERBOSE)
 516                    Debug.Log($"{Time.frameCount}: Load parcel scene (id: {newScene.sceneData.sceneNumber})");
 34517            }
 518
 34519            ProfilingEvents.OnMessageProcessEnds?.Invoke(MessagingTypes.SCENE_LOAD);
 34520        }
 521
 522        private async Task RequestSceneContentCategory(ParcelScene parcelScene)
 523        {
 34524            if (!isContentModerationFeatureEnabled)
 34525                return;
 526
 0527            var sceneInfo = MinimapMetadata.GetMetadata().GetSceneInfo(parcelScene.sceneData.basePosition.x, parcelScene
 0528            if (sceneInfo is { name: EMPTY_PARCEL_NAME })
 0529                return;
 530
 531            try
 532            {
 533                string placeId;
 534                string placeContentRating;
 535
 0536                await UniTask
 0537                     .WaitUntil(() => DataStore.i.realm.realmWasSetByFirstTime.Get(), cancellationToken: requestPlaceCts
 538                     .Timeout(TimeSpan.FromSeconds(REQUEST_PLACE_TIME_OUT));
 539
 0540                if (!DataStore.i.common.isWorld.Get())
 541                {
 0542                    var associatedPlace = await placesAPIService
 543                                               .GetPlace(parcelScene.sceneData.basePosition, requestPlaceCts.Token)
 544                                               .Timeout(TimeSpan.FromSeconds(REQUEST_PLACE_TIME_OUT));
 545
 0546                    placeId = associatedPlace.id;
 0547                    placeContentRating = associatedPlace.content_rating;
 548                }
 549                else
 550                {
 0551                    var associatedWorld = await worldsAPIService
 552                                               .GetWorld(DataStore.i.realm.realmName.Get(), requestPlaceCts.Token)
 553                                               .Timeout(TimeSpan.FromSeconds(REQUEST_PLACE_TIME_OUT));
 0554                    placeId = associatedWorld.id;
 0555                    placeContentRating = associatedWorld.content_rating;
 556                }
 557
 0558                parcelScene.SetAssociatedPlace(placeId);
 559
 560                switch (placeContentRating)
 561                {
 562                    case "A" or "M":
 0563                        parcelScene.SetContentCategory(SceneContentCategory.ADULT);
 0564                        break;
 565                    case "R":
 0566                        parcelScene.SetContentCategory(SceneContentCategory.RESTRICTED);
 0567                        break;
 568                    default:
 0569                        parcelScene.SetContentCategory(SceneContentCategory.TEEN);
 570                        break;
 571                }
 0572            }
 0573            catch (Exception ex)
 574            {
 0575                parcelScene.SetContentCategory(SceneContentCategory.TEEN);
 576
 0577                if (ex is not NotAPlaceException)
 0578                    Debug.LogError($"An error occurred while requesting the content category for ({parcelScene.sceneData
 0579            }
 34580        }
 581
 582        public void UpdateParcelScenesExecute(string scenePayload)
 583        {
 584            LoadParcelScenesMessage.UnityParcelScene sceneData;
 585
 0586            ProfilingEvents.OnMessageDecodeStart?.Invoke(MessagingTypes.SCENE_UPDATE);
 0587            sceneData = Utils.SafeFromJson<LoadParcelScenesMessage.UnityParcelScene>(scenePayload);
 0588            ProfilingEvents.OnMessageDecodeEnds?.Invoke(MessagingTypes.SCENE_UPDATE);
 589
 0590            IWorldState worldState = Environment.i.world.state;
 591
 0592            if (worldState.TryGetScene(sceneData.sceneNumber, out IParcelScene sceneInterface))
 593            {
 0594                ParcelScene scene = sceneInterface as ParcelScene;
 0595                scene.SetUpdateData(sceneData);
 596            }
 0597            else { LoadParcelScenesExecute(scenePayload); }
 0598        }
 599
 600        public void UpdateParcelScenesExecute(LoadParcelScenesMessage.UnityParcelScene scene)
 601        {
 0602            if (scene == null || scene.sceneNumber <= 0)
 0603                return;
 604
 0605            var sceneToLoad = scene;
 606
 0607            ProfilingEvents.OnMessageProcessStart?.Invoke(MessagingTypes.SCENE_UPDATE);
 608
 0609            ParcelScene parcelScene = Environment.i.world.state.GetScene(sceneToLoad.sceneNumber) as ParcelScene;
 610
 0611            if (parcelScene != null)
 0612                parcelScene.SetUpdateData(sceneToLoad);
 613
 0614            ProfilingEvents.OnMessageProcessEnds?.Invoke(MessagingTypes.SCENE_UPDATE);
 0615        }
 616
 617        public void UnloadScene(int sceneNumber)
 618        {
 2619            var queuedMessage = new QueuedSceneMessage()
 620                { type = QueuedSceneMessage.Type.UNLOAD_PARCEL, sceneNumber = sceneNumber };
 621
 2622            ProfilingEvents.OnMessageWillQueue?.Invoke(MessagingTypes.SCENE_DESTROY);
 623
 2624            messagingControllersManager.ForceEnqueueToGlobal(MessagingBusType.INIT, queuedMessage);
 2625            messagingControllersManager.RemoveController(sceneNumber);
 2626        }
 627
 628        public void UnloadParcelSceneExecute(int sceneNumber)
 629        {
 267630            ProfilingEvents.OnMessageProcessStart?.Invoke(MessagingTypes.SCENE_DESTROY);
 631
 267632            IWorldState worldState = Environment.i.world.state;
 633
 267634            if (!worldState.TryGetScene(sceneNumber, out ParcelScene scene))
 0635                return;
 636
 267637            CommonScriptableObjects.rendererState.RemoveLock(scene);
 267638            worldState.RemoveScene(sceneNumber);
 639
 267640            if (scene.isPortableExperience)
 641            {
 2642                portableExperienceIds.Remove(scene.sceneData.id);
 643
 2644                string iconUrl = null;
 645
 2646                if (!string.IsNullOrEmpty(scene.sceneData.iconUrl))
 0647                    iconUrl = scene.contentProvider.GetContentsUrl(scene.sceneData.iconUrl);
 648
 2649                disabledPortableExperiences.AddOrSet(scene.sceneData.id,
 650                    (name: scene.sceneName, description: scene.sceneData.description, icon: iconUrl));
 651            }
 652
 653            // Remove messaging controller for unloaded scene
 267654            messagingControllersManager.RemoveController(sceneNumber);
 655
 267656            scene.Cleanup(!CommonScriptableObjects.rendererState.Get());
 657
 658            if (VERBOSE) { Debug.Log($"{Time.frameCount} : Destroying scene {scene.sceneData.basePosition}"); }
 659
 267660            Environment.i.world.blockersController.SetupWorldBlockers();
 661
 267662            ProfilingEvents.OnMessageProcessEnds?.Invoke(MessagingTypes.SCENE_DESTROY);
 267663            OnSceneRemoved?.Invoke(scene);
 2664        }
 665
 666        public void UnloadAllScenes(bool includePersistent = false)
 667        {
 442668            var worldState = Environment.i.world.state;
 669
 670            // since the list was changing by this foreach, we make a copy
 442671            var list = worldState.GetLoadedScenes().ToArray();
 672
 1392673            foreach (var kvp in list)
 674            {
 254675                if (kvp.Value.isPersistent && !includePersistent)
 676                    continue;
 677
 254678                UnloadParcelSceneExecute(kvp.Key);
 679            }
 442680        }
 681
 682        public void LoadParcelScenes(string decentralandSceneJSON)
 683        {
 28684            var queuedMessage = new QueuedSceneMessage()
 685            {
 686                type = QueuedSceneMessage.Type.LOAD_PARCEL,
 687                message = decentralandSceneJSON
 688            };
 689
 28690            ProfilingEvents.OnMessageWillQueue?.Invoke(MessagingTypes.SCENE_LOAD);
 691
 28692            messagingControllersManager.ForceEnqueueToGlobal(MessagingBusType.INIT, queuedMessage);
 693
 694            if (VERBOSE)
 695                Debug.Log($"{Time.frameCount} : Load parcel scene queue {decentralandSceneJSON}");
 28696        }
 697
 698        public void UpdateParcelScenes(string decentralandSceneJSON)
 699        {
 0700            var queuedMessage = new QueuedSceneMessage()
 701                { type = QueuedSceneMessage.Type.UPDATE_PARCEL, message = decentralandSceneJSON };
 702
 0703            ProfilingEvents.OnMessageWillQueue?.Invoke(MessagingTypes.SCENE_UPDATE);
 704
 0705            messagingControllersManager.ForceEnqueueToGlobal(MessagingBusType.INIT, queuedMessage);
 0706        }
 707
 708        public void UnloadAllScenesQueued()
 709        {
 0710            var queuedMessage = new QueuedSceneMessage() { type = QueuedSceneMessage.Type.UNLOAD_SCENES };
 711
 0712            ProfilingEvents.OnMessageWillQueue?.Invoke(MessagingTypes.SCENE_DESTROY);
 713
 0714            Environment.i.messaging.manager.ForceEnqueueToGlobal(MessagingBusType.INIT, queuedMessage);
 0715        }
 716
 717        public void CreateGlobalScene(CreateGlobalSceneMessage globalScene)
 718        {
 719            void CreateGlobalSceneInternal(CreateGlobalSceneMessage globalScene)
 720            {
 721#if UNITY_EDITOR
 7722                DebugConfig debugConfig = DataStore.i.debugConfig;
 723
 7724                if (debugConfig.soloScene && debugConfig.ignoreGlobalScenes)
 0725                    return;
 726#endif
 727
 728                // NOTE(Brian): We should remove this line. SceneController is a runtime core class.
 729                //              It should never have references to UI systems or higher level systems.
 7730                if (globalScene.isPortableExperience && !isPexViewerInitialized.Get())
 731                {
 0732                    Debug.LogError(
 733                        "Portable experiences are trying to be added before the system is initialized!. scene number: " 
 734                        globalScene.sceneNumber);
 735
 0736                    return;
 737                }
 738
 7739                int newGlobalSceneNumber = globalScene.sceneNumber;
 7740                IWorldState worldState = Environment.i.world.state;
 741
 7742                if (worldState.ContainsScene(newGlobalSceneNumber))
 0743                    return;
 744
 7745                var newGameObject = new GameObject("Global Scene - " + newGlobalSceneNumber);
 746
 7747                var newScene = newGameObject.AddComponent<GlobalScene>();
 7748                newScene.unloadWithDistance = false;
 7749                newScene.isPersistent = true;
 7750                newScene.sceneName = globalScene.name;
 7751                newScene.isPortableExperience = globalScene.isPortableExperience;
 752
 7753                LoadParcelScenesMessage.UnityParcelScene sceneData = new LoadParcelScenesMessage.UnityParcelScene
 754                {
 755                    id = globalScene.id,
 756                    sceneNumber = newGlobalSceneNumber,
 757                    basePosition = new Vector2Int(0, 0),
 758                    baseUrl = globalScene.baseUrl,
 759                    contents = globalScene.contents,
 760                    sdk7 = globalScene.sdk7,
 761                    requiredPermissions = globalScene.requiredPermissions,
 762                    allowedMediaHostnames = globalScene.allowedMediaHostnames,
 763                    description = globalScene.description,
 764                    iconUrl = globalScene.icon,
 765                };
 766
 7767                newScene.SetData(sceneData).Forget();
 768
 7769                if (!string.IsNullOrEmpty(globalScene.icon))
 0770                    newScene.iconUrl = newScene.contentProvider.GetContentsUrl(globalScene.icon);
 771
 7772                worldState.AddScene(newScene);
 7773                OnNewSceneAdded?.Invoke(newScene);
 774
 7775                if (globalScene.isPortableExperience)
 776                {
 2777                    disabledPortableExperiences.Remove(sceneData.id);
 778
 2779                    if (!portableExperienceIds.Contains(sceneData.id))
 2780                        portableExperienceIds.Add(sceneData.id);
 781
 2782                    portableExperiencesAnalytics.Spawn(sceneData.id);
 783                }
 784
 7785                messagingControllersManager.AddControllerIfNotExists(this, newGlobalSceneNumber, isGlobal: true);
 786
 787                if (VERBOSE)
 788                    Debug.Log($"Creating Global scene {newGlobalSceneNumber}");
 7789            }
 790
 791            (string name, string description, string icon) CreateDisabledPortableExperience(CreateGlobalSceneMessage glo
 792            {
 0793                string iconUrl = null;
 794
 0795                if (!string.IsNullOrEmpty(globalScene.icon))
 796                {
 0797                    ContentProvider contentProvider = new ContentProvider
 798                    {
 799                        baseUrl = globalScene.baseUrl,
 800                        contents = globalScene.contents,
 801                        sceneCid = globalScene.id,
 802                    };
 803
 0804                    contentProvider.BakeHashes();
 805
 0806                    iconUrl = contentProvider.GetContentsUrl(globalScene.icon);
 807                }
 808
 0809                return (name: globalScene.name, description: globalScene.description, icon: iconUrl);
 810            }
 811
 812            void DisablePortableExperience(string pxId,
 813                (string name, string description, string icon) disabledPx)
 814            {
 0815                disabledPortableExperiences.AddOrSet(pxId, disabledPx);
 816
 0817                WebInterface.SetDisabledPortableExperiences(
 818                    disabledPortableExperiences.GetKeys().ToArray());
 0819            }
 820
 821            void ConfirmPortableExperience(CreateGlobalSceneMessage globalScene)
 822            {
 0823                (string name, string description, string icon) disabledPx = CreateDisabledPortableExperience(globalScene
 824
 0825                disabledPortableExperiences.AddOrSet(globalScene.id, disabledPx);
 826
 0827                pendingPortableExperienceToBeConfirmed.Set(new ExperiencesConfirmationData
 828                {
 829                    Experience = new ExperiencesConfirmationData.ExperienceMetadata
 830                    {
 831                        Permissions = globalScene.requiredPermissions,
 832                        ExperienceId = globalScene.id,
 833                        ExperienceName = globalScene.name,
 834                        IconUrl = disabledPx.icon,
 835                        Description = globalScene.description,
 836                    },
 0837                    OnAcceptCallback = () => CreateGlobalSceneInternal(globalScene),
 0838                    OnRejectCallback = () => DisablePortableExperience(globalScene.id, disabledPx),
 839                });
 0840            }
 841
 7842            if (globalScene.isPortableExperience)
 843            {
 2844                string pxId = globalScene.id;
 2845                bool confirmPx = false;
 2846                bool disablePx = false;
 847
 2848                if (DataStore.i.featureFlags.flags.Get().IsFeatureEnabled("px_confirm_enabled"))
 849                {
 0850                    if (!IsPortableExperienceInWhiteList(pxId))
 851                    {
 0852                        confirmPx = !IsPortableExperienceAlreadyConfirmed(pxId);
 853
 0854                        disablePx = IsPortableExperienceAlreadyConfirmed(pxId)
 855                                    && !ShouldForceAcceptPortableExperience(pxId)
 856                                    && !IsPortableExperienceConfirmedAndAccepted(pxId);
 857                    }
 858                }
 859
 2860                IWorldState worldState = Environment.i.world.state;
 2861                IParcelScene scene = worldState.GetScene(worldState.GetCurrentSceneNumber());
 862
 2863                if (scene != null
 864                    && scene.sceneData.scenePortableExperienceFeatureToggles == ScenePortableExperienceFeatureToggles.Di
 865                {
 0866                    confirmPx = false;
 0867                    disablePx = true;
 868                }
 869
 2870                if (confirmPx)
 0871                    ConfirmPortableExperience(globalScene);
 2872                else if (disablePx)
 0873                    DisablePortableExperience(globalScene.id, CreateDisabledPortableExperience(globalScene));
 874                else
 2875                    CreateGlobalSceneInternal(globalScene);
 876            }
 877            else
 5878                CreateGlobalSceneInternal(globalScene);
 5879        }
 880
 881        public void IsolateScene(IParcelScene sceneToActive)
 882        {
 0883            foreach (IParcelScene scene in Environment.i.world.state.GetScenesSortedByDistance())
 884            {
 0885                if (scene != sceneToActive)
 0886                    scene.GetSceneTransform().gameObject.SetActive(false);
 887            }
 0888        }
 889
 890        public void ReIntegrateIsolatedScene()
 891        {
 0892            foreach (IParcelScene scene in Environment.i.world.state.GetScenesSortedByDistance()) { scene.GetSceneTransf
 0893        }
 894
 895        //======================================================================
 896#endregion
 897
 898        //======================================================================
 899
 440900        public bool prewarmEntitiesPool { get; set; } = true;
 901
 902        private bool sceneSortDirty = false;
 440903        private bool positionDirty = true;
 904        private int lastSortFrame = 0;
 905
 906        public event Action OnSortScenes;
 907        public event Action<IParcelScene, string> OnOpenExternalUrlRequest;
 908        public event Action<IParcelScene> OnNewSceneAdded;
 909        public event Action<IParcelScene> OnSceneRemoved;
 910
 440911        private Vector2Int currentGridSceneCoordinate = new Vector2Int(EnvironmentSettings.MORDOR_SCALAR, EnvironmentSet
 912
 913        private bool ShouldForceAcceptPortableExperience(string pxId) =>
 0914            DataStore.i.world.forcePortableExperience.Equals(pxId);
 915
 916        private bool IsPortableExperienceConfirmedAndAccepted(string pxId) =>
 0917            confirmedExperiencesRepository.Get(pxId);
 918
 919        private bool IsPortableExperienceAlreadyConfirmed(string pxId) =>
 0920            confirmedExperiencesRepository.Contains(pxId);
 921
 922        private bool IsPortableExperienceInWhiteList(string pxId)
 923        {
 0924            FeatureFlag flags = DataStore.i.featureFlags.flags.Get();
 0925            FeatureFlagVariantPayload payload = flags.GetFeatureFlagVariantPayload("initial_portable_experiences:calenda
 926
 0927            if (payload == null) return false;
 928
 0929            string[] whitelistedPxs = JsonConvert.DeserializeObject<string[]>(payload.value);
 930
 0931            if (whitelistedPxs == null) return false;
 932
 0933            for (var i = 0; i < whitelistedPxs.Length; i++)
 0934                if (whitelistedPxs[i].StartsWith(pxId)) return true;
 935
 0936            return false;
 937        }
 938
 939        private void OnAdultContentSettingChange(bool isEnabled, bool previousIsEnabled)
 940        {
 1941            if (!isContentModerationFeatureEnabled)
 1942                return;
 943
 0944            if (isEnabled == previousIsEnabled)
 0945                return;
 946
 0947            var loadedScenes = Environment.i.world.state.GetLoadedScenes();
 0948            reloadAdultScenesCts = reloadAdultScenesCts.SafeRestart();
 0949            ReloadAdultScenesAsync(loadedScenes.ToList(), reloadAdultScenesCts.Token).Forget();
 0950        }
 951
 952        private async UniTaskVoid ReloadAdultScenesAsync(List<KeyValuePair<int, IParcelScene>> loadedScenes, Cancellatio
 953        {
 0954            foreach (KeyValuePair<int,IParcelScene> scene in loadedScenes)
 955            {
 0956                if (scene.Value.contentCategory != SceneContentCategory.ADULT)
 957                    continue;
 958
 0959                WebInterface.ReloadScene(scene.Value.sceneData.basePosition);
 0960                await Task.Delay(TimeSpan.FromSeconds(0.5f), ct);
 961
 0962                if (ct.IsCancellationRequested)
 0963                    return;
 964            }
 0965        }
 966    }
 967}

Methods/Properties

isPexViewerInitialized()
messagingControllersManager()
disabledPortableExperiences()
portableExperienceIds()
pendingPortableExperienceToBeConfirmed()
portableExperiencesAnalytics()
placesAPIService()
worldsAPIService()
isContentModerationFeatureEnabled()
entityIdHelper()
SceneController(DCL.World.PortableExperiences.IConfirmedExperiencesRepository)
enabled()
enabled(System.Boolean)
Initialize()
OnDebugModeSet(System.Boolean, System.Boolean)
Dispose()
Update()
LateUpdate()
deferredMessagesDecoding()
deferredMessagesDecoding(System.Boolean)
ProcessMessage(DCL.QueuedSceneMessage_Scene, UnityEngine.CustomYieldInstruction&)
ProcessMessage(DCL.Controllers.ParcelScene, System.String, System.Object, UnityEngine.CustomYieldInstruction&)
ParseQuery(System.Object, System.Int32)
SendSceneMessage(System.String)
Decode(System.String, DCL.QueuedSceneMessage_Scene)
EnqueueChunk(System.String)
EnqueueSceneMessage(DCL.QueuedSceneMessage_Scene)
SendSceneReady(System.Int32)
SetPositionDirty(UnityEngine.Vector2Int, UnityEngine.Vector2Int)
SortScenesByDistance()
OnCurrentSceneNumberChange(System.Int32, System.Int32)
LoadParcelScenesExecute(System.String)
LoadUnityParcelScene()
RequestSceneContentCategory()
UpdateParcelScenesExecute(System.String)
UpdateParcelScenesExecute(DCL.Models.LoadParcelScenesMessage/UnityParcelScene)
UnloadScene(System.Int32)
UnloadParcelSceneExecute(System.Int32)
UnloadAllScenes(System.Boolean)
LoadParcelScenes(System.String)
UpdateParcelScenes(System.String)
UnloadAllScenesQueued()
CreateGlobalScene(DCL.Models.CreateGlobalSceneMessage)
IsolateScene(DCL.Controllers.IParcelScene)
ReIntegrateIsolatedScene()
prewarmEntitiesPool()
prewarmEntitiesPool(System.Boolean)
ShouldForceAcceptPortableExperience(System.String)
IsPortableExperienceConfirmedAndAccepted(System.String)
IsPortableExperienceAlreadyConfirmed(System.String)
IsPortableExperienceInWhiteList(System.String)
OnAdultContentSettingChange(System.Boolean, System.Boolean)
ReloadAdultScenesAsync()