< Summary

Class:DCL.Controllers.SceneBoundsChecker
Assembly:DCL.Runtime
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/WorldRuntime/SceneBoundariesController/SceneBoundsChecker.cs
Covered lines:150
Uncovered lines:21
Coverable lines:171
Total lines:380
Line coverage:87.7% (150 of 171)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SceneBoundsChecker(...)0%220100%
Initialize()0%110100%
SetFeedbackStyle(...)0%110100%
GetFeedbackStyle()0%110100%
GetOriginalMaterials(...)0%2100%
CheckEntities()0%770100%
Restart()0%110100%
Start()0%2.032080%
Stop()0%220100%
Dispose()0%110100%
AddEntityToBeChecked(...)0%880100%
RemoveEntity(...)0%5.075085.71%
WasAddedAsPersistent(...)0%110100%
RunEntityEvaluation(...)0%110100%
RunEntityEvaluation(...)0%9.049092.31%
EvaluateMeshBounds(...)0%6.026091.67%
EvaluateEntityPosition(...)0%330100%
EvaluateAvatarMeshBounds(...)0%12300%
SetEntityInsideBoundariesState(...)0%330100%
HasMesh(...)0%440100%
IsEntityMeshInsideSceneBoundaries(...)0%550100%
AreSubmeshesInsideBoundaries(...)0%440100%
AreCollidersInsideBoundaries(...)0%440100%
SetMeshesAndComponentsInsideBoundariesState(...)0%110100%
SetEntityMeshesInsideBoundariesState(...)0%110100%
SetEntityCollidersInsideBoundariesState(...)0%770100%
SetComponentsInsideBoundariesValidState(...)0%440100%

File(s)

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

#LineLine coverage
 1using DCL.Models;
 2using UnityEngine;
 3using System.Collections.Generic;
 4using System.Collections;
 5using System;
 6
 7namespace DCL.Controllers
 8{
 9    public class SceneBoundsChecker : ISceneBoundsChecker
 10    {
 11        public event Action<IDCLEntity, bool> OnEntityBoundsCheckerStatusChanged;
 100412        public bool enabled => entitiesCheckRoutine != null;
 70913        public float timeBetweenChecks { get; set; } = 0.5f;
 214        public int entitiesToCheckCount => entitiesToCheck.Count;
 15
 16        private const bool VERBOSE = false;
 67017        private Logger logger = new Logger("SceneBoundsChecker") {verboseEnabled = VERBOSE};
 67018        private HashSet<IDCLEntity> entitiesToCheck = new HashSet<IDCLEntity>();
 67019        private HashSet<IDCLEntity> checkedEntities = new HashSet<IDCLEntity>();
 67020        private HashSet<IDCLEntity> persistentEntities = new HashSet<IDCLEntity>();
 21        private ISceneBoundsFeedbackStyle feedbackStyle;
 22        private Coroutine entitiesCheckRoutine = null;
 23        private float lastCheckTime;
 24
 25        public void Initialize()
 26        {
 67027            Start();
 67028        }
 29
 67030        public SceneBoundsChecker(ISceneBoundsFeedbackStyle feedbackStyle = null)
 31        {
 67032            this.feedbackStyle = feedbackStyle ?? new SceneBoundsFeedbackStyle_Simple();
 67033        }
 34
 35        public void SetFeedbackStyle(ISceneBoundsFeedbackStyle feedbackStyle)
 36        {
 5437            this.feedbackStyle.CleanFeedback();
 5438            this.feedbackStyle = feedbackStyle;
 5439            Restart();
 5440        }
 41
 7842        public ISceneBoundsFeedbackStyle GetFeedbackStyle() { return feedbackStyle; }
 43
 044        public List<Material> GetOriginalMaterials(MeshesInfo meshesInfo) { return feedbackStyle.GetOriginalMaterials(me
 45
 46
 47        // TODO: Improve MessagingControllersManager.i.timeBudgetCounter usage once we have the centralized budget contr
 48        private IEnumerator CheckEntities()
 49        {
 685950            while (true)
 51            {
 758352                float elapsedTime = Time.realtimeSinceStartup - lastCheckTime;
 758353                if ((entitiesToCheck.Count > 0) && (timeBetweenChecks <= 0f || elapsedTime >= timeBetweenChecks))
 54                {
 55                    //TODO(Brian): Remove later when we implement a centralized way of handling time budgets
 7456                    var messagingManager = Environment.i.messaging.manager as MessagingControllersManager;
 57
 58                    void processEntitiesList(HashSet<IDCLEntity> entities)
 59                    {
 7460                        if (messagingManager != null && messagingManager.timeBudgetCounter <= 0f)
 61                        {
 62                            if(VERBOSE)
 63                                logger.Verbose("Time budget reached, escaping entities processing until next iteration..
 064                            return;
 65                        }
 66
 7467                        using HashSet<IDCLEntity>.Enumerator iterator = entities.GetEnumerator();
 15268                        while (iterator.MoveNext())
 69                        {
 7870                            if (messagingManager != null && messagingManager.timeBudgetCounter <= 0f)
 71                            {
 072                                if(VERBOSE)
 73                                    logger.Verbose("Time budget reached, escaping entities processing until next iterati
 74                                return;
 75                            }
 76
 7877                            float startTime = Time.realtimeSinceStartup;
 78
 7879                            RunEntityEvaluation(iterator.Current);
 7880                            checkedEntities.Add(iterator.Current);
 81
 7882                            float finishTime = Time.realtimeSinceStartup;
 83
 7884                            if ( messagingManager != null )
 7885                                messagingManager.timeBudgetCounter -= (finishTime - startTime);
 86                        }
 14887                    }
 88
 7489                    processEntitiesList(entitiesToCheck);
 90
 91                    // As we can't modify the hashset while traversing it, we keep track of the entities that should be 
 7492                    using (var iterator = checkedEntities.GetEnumerator())
 93                    {
 15294                        while (iterator.MoveNext())
 95                        {
 7896                            RemoveEntity(iterator.Current, removeIfPersistent: false, resetState: false);
 97                        }
 7498                    }
 99
 100                    if(VERBOSE)
 101                        logger.Verbose($"Finished checking entities: checked entities {checkedEntities.Count}; entitiesT
 102
 74103                    checkedEntities.Clear();
 104
 74105                    lastCheckTime = Time.realtimeSinceStartup;
 106                }
 107
 7583108                yield return null;
 109            }
 110        }
 111
 112        public void Restart()
 113        {
 54114            Stop();
 54115            Start();
 54116        }
 117
 118        public void Start()
 119        {
 724120            if (entitiesCheckRoutine != null)
 0121                return;
 122
 724123            lastCheckTime = Time.realtimeSinceStartup;
 724124            entitiesCheckRoutine = CoroutineStarter.Start(CheckEntities());
 724125        }
 126
 127        public void Stop()
 128        {
 745129            if (entitiesCheckRoutine == null)
 21130                return;
 131
 724132            CoroutineStarter.Stop(entitiesCheckRoutine);
 724133            entitiesCheckRoutine = null;
 724134        }
 135
 136        public void Dispose()
 137        {
 670138            Stop();
 670139        }
 140
 141        public void AddEntityToBeChecked(IDCLEntity entity, bool isPersistent = false, bool runPreliminaryEvaluation = f
 142        {
 1279143            if (!enabled || (entity.scene != null && entity.scene.isPersistent))
 969144                return;
 145
 310146            if (runPreliminaryEvaluation)
 147            {
 148                // The outer bounds check is cheaper than the regular check
 245149                RunEntityEvaluation(entity, onlyOuterBoundsCheck: true);
 150
 151                // No need to add the entity to be checked later if we already found it outside scene outer boundaries.
 152                // When the correct events are triggered again, the entity will be checked again.
 245153                if (!isPersistent && !entity.isInsideSceneOuterBoundaries)
 89154                    return;
 155            }
 156
 221157            entitiesToCheck.Add(entity);
 158
 221159            if (isPersistent)
 6160                persistentEntities.Add(entity);
 221161        }
 162
 163        public void RemoveEntity(IDCLEntity entity, bool removeIfPersistent = false, bool resetState = false)
 164        {
 545165            if (!enabled || (!removeIfPersistent && persistentEntities.Contains(entity)))
 0166                return;
 167
 545168            entitiesToCheck.Remove(entity);
 545169            persistentEntities.Remove(entity);
 170
 545171            if(resetState)
 465172                SetMeshesAndComponentsInsideBoundariesState(entity, true);
 545173        }
 174
 7175        public bool WasAddedAsPersistent(IDCLEntity entity) { return persistentEntities.Contains(entity); }
 176
 177        // TODO: When we remove the DCLBuilderEntity class we'll be able to remove this overload
 178        public void RunEntityEvaluation(IDCLEntity entity)
 179        {
 92180            RunEntityEvaluation(entity, false);
 92181        }
 182
 183        public void RunEntityEvaluation(IDCLEntity entity, bool onlyOuterBoundsCheck)
 184        {
 348185            if (entity == null || entity.gameObject == null || entity.scene == null || entity.scene.isPersistent)
 7186                return;
 187
 188            // Recursively evaluate entity children as well, we need to check this up front because this entity may not 
 341189            if (entity.children.Count > 0)
 190            {
 9191                using (var iterator = entity.children.GetEnumerator())
 192                {
 18193                    while (iterator.MoveNext())
 194                    {
 9195                        RunEntityEvaluation(iterator.Current.Value, onlyOuterBoundsCheck);
 196                    }
 9197                }
 198            }
 199
 341200            if (HasMesh(entity)) // If it has a mesh we don't evaluate its position due to artists "pivot point sloppine
 163201                EvaluateMeshBounds(entity, onlyOuterBoundsCheck);
 178202            else if (entity.scene.componentsManagerLegacy.HasComponent(entity, CLASS_ID_COMPONENT.AVATAR_SHAPE)) // Avat
 0203                EvaluateAvatarMeshBounds(entity, onlyOuterBoundsCheck);
 204            else
 178205                EvaluateEntityPosition(entity, onlyOuterBoundsCheck);
 178206        }
 207
 208        private void EvaluateMeshBounds(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 209        {
 210            // TODO: Can we cache the MaterialTransitionController somewhere to avoid this GetComponent() call?
 211            // If the mesh is being loaded we should skip the evaluation (it will be triggered again later when the load
 163212            if (entity.meshRootGameObject.GetComponent<MaterialTransitionController>()) // the object's MaterialTransiti
 0213                return;
 214
 163215            var loadWrapper = Environment.i.world.state.GetLoaderForEntity(entity);
 163216            if (loadWrapper != null && !loadWrapper.alreadyLoaded)
 10217                return;
 218
 153219            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entity.meshesInfo.mergedBoun
 220
 153221            if (!entity.isInsideSceneOuterBoundaries)
 99222                SetMeshesAndComponentsInsideBoundariesState(entity, false);
 223
 153224            if (onlyOuterBoundsCheck)
 104225                return;
 226
 49227            SetMeshesAndComponentsInsideBoundariesState(entity, IsEntityMeshInsideSceneBoundaries(entity));
 49228        }
 229
 230        private void EvaluateEntityPosition(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 231        {
 178232            Vector3 entityGOPosition = entity.gameObject.transform.position;
 178233            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entityGOPosition);
 234
 178235            if (!entity.isInsideSceneOuterBoundaries)
 236            {
 35237                SetComponentsInsideBoundariesValidState(entity, false);
 35238                SetEntityInsideBoundariesState(entity, false);
 239            }
 240
 178241            if (onlyOuterBoundsCheck)
 139242                return;
 243
 39244            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entityGOPosition + CommonScriptableObjects.wo
 39245            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 39246            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 39247        }
 248
 249        private void EvaluateAvatarMeshBounds(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 250        {
 0251            Vector3 entityGOPosition = entity.gameObject.transform.position;
 252
 253            // Heuristic using the entity scale for the size of the avatar bounds, otherwise we should configure the
 254            // entity's meshRootGameObject, etc. after its GPU skinning runs and use the regular entity mesh evaluation
 0255            Bounds avatarBounds = new Bounds();
 0256            avatarBounds.center = entityGOPosition;
 0257            avatarBounds.size = entity.gameObject.transform.lossyScale;
 258
 0259            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(avatarBounds);
 260
 0261            if (!entity.isInsideSceneOuterBoundaries)
 262            {
 0263                SetComponentsInsideBoundariesValidState(entity, false);
 0264                SetEntityInsideBoundariesState(entity, false);
 265            }
 266
 0267            if (onlyOuterBoundsCheck)
 0268                return;
 269
 0270            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(avatarBounds);
 0271            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 0272            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 0273        }
 274
 275        private void SetEntityInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 276        {
 687277            if (entity.isInsideSceneBoundaries == isInsideBoundaries)
 561278                return;
 279
 126280            entity.isInsideSceneBoundaries = isInsideBoundaries;
 126281            OnEntityBoundsCheckerStatusChanged?.Invoke(entity, isInsideBoundaries);
 3282        }
 283
 284        private bool HasMesh(IDCLEntity entity)
 285        {
 341286            return entity.meshRootGameObject != null
 287                    && (entity.meshesInfo.colliders.Count > 0
 288                    || (entity.meshesInfo.renderers != null
 289                    && entity.meshesInfo.renderers.Length > 0));
 290        }
 291
 292        public bool IsEntityMeshInsideSceneBoundaries(IDCLEntity entity)
 293        {
 90294            if (entity.meshesInfo == null
 295                || entity.meshesInfo.meshRootGameObject == null
 296                || entity.meshesInfo.mergedBounds == null)
 25297                return false;
 298
 299            // 1st check (full mesh AABB)
 65300            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entity.meshesInfo.mergedBounds);
 301
 302            // 2nd check (submeshes & colliders AABB)
 65303            if (!isInsideBoundaries)
 304            {
 30305                isInsideBoundaries = AreSubmeshesInsideBoundaries(entity) && AreCollidersInsideBoundaries(entity);
 306            }
 307
 65308            return isInsideBoundaries;
 309        }
 310
 311        private bool AreSubmeshesInsideBoundaries(IDCLEntity entity)
 312        {
 70313            for (int i = 0; i < entity.meshesInfo.renderers.Length; i++)
 314            {
 31315                Renderer renderer = entity.meshesInfo.renderers[i];
 31316                if (renderer == null)
 317                    continue;
 318
 31319                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(renderer.bounds, renderer.transf
 26320                    return false;
 321            }
 322
 4323            return true;
 324        }
 325
 326        private bool AreCollidersInsideBoundaries(IDCLEntity entity)
 327        {
 13328            foreach (Collider collider in entity.meshesInfo.colliders)
 329            {
 4330                if (collider == null)
 331                    continue;
 332
 4333                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(collider.bounds, collider.transf
 3334                    return false;
 335            }
 336
 1337            return true;
 3338        }
 339
 340        private void SetMeshesAndComponentsInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 341        {
 613342            SetEntityMeshesInsideBoundariesState(entity.meshesInfo, isInsideBoundaries);
 613343            SetEntityCollidersInsideBoundariesState(entity.meshesInfo, isInsideBoundaries);
 613344            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 345
 346            // Should always be set last as entity.isInsideSceneBoundaries is checked to avoid re-running code unnecessa
 613347            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 613348        }
 349
 350        private void SetEntityMeshesInsideBoundariesState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 351        {
 613352            feedbackStyle.ApplyFeedback(meshesInfo, isInsideBoundaries);
 613353        }
 354
 355        private void SetEntityCollidersInsideBoundariesState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 356        {
 613357            if (meshesInfo == null || meshesInfo.colliders.Count == 0 || !meshesInfo.currentShape.HasCollisions())
 394358                return;
 359
 978360            foreach (Collider collider in meshesInfo.colliders)
 361            {
 270362                if (collider == null) continue;
 363
 270364                if (collider.enabled != isInsideBoundaries)
 140365                    collider.enabled = isInsideBoundaries;
 366            }
 219367        }
 368
 369        private void SetComponentsInsideBoundariesValidState(IDCLEntity entity, bool isInsideBoundaries)
 370        {
 687371            if(entity.isInsideSceneBoundaries == isInsideBoundaries || !DataStore.i.sceneBoundariesChecker.componentsChe
 684372                return;
 373
 12374            foreach (IOutOfSceneBoundariesHandler component in DataStore.i.sceneBoundariesChecker.componentsCheckSceneBo
 375            {
 3376                component.UpdateOutOfBoundariesState(isInsideBoundaries);
 377            }
 3378        }
 379    }
 380}