< 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:147
Uncovered lines:6
Coverable lines:153
Total lines:351
Line coverage:96% (147 of 153)
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%880100%
EvaluateMeshBounds(...)0%6.036090.91%
EvaluateEntityPosition(...)0%330100%
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;
 99512        public bool enabled => entitiesCheckRoutine != null;
 70213        public float timeBetweenChecks { get; set; } = 0.5f;
 214        public int entitiesToCheckCount => entitiesToCheck.Count;
 15
 16        private const bool VERBOSE = false;
 66617        private Logger logger = new Logger("SceneBoundsChecker") {verboseEnabled = VERBOSE};
 66618        private HashSet<IDCLEntity> entitiesToCheck = new HashSet<IDCLEntity>();
 66619        private HashSet<IDCLEntity> checkedEntities = new HashSet<IDCLEntity>();
 66620        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        {
 66627            Start();
 66628        }
 29
 66630        public SceneBoundsChecker(ISceneBoundsFeedbackStyle feedbackStyle = null)
 31        {
 66632            this.feedbackStyle = feedbackStyle ?? new SceneBoundsFeedbackStyle_Simple();
 66633        }
 34
 35        public void SetFeedbackStyle(ISceneBoundsFeedbackStyle feedbackStyle)
 36        {
 3537            this.feedbackStyle.CleanFeedback();
 3538            this.feedbackStyle = feedbackStyle;
 3539            Restart();
 3540        }
 41
 7142        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        {
 681350            while (true)
 51            {
 751452                float elapsedTime = Time.realtimeSinceStartup - lastCheckTime;
 751453                if ((entitiesToCheck.Count > 0) && (timeBetweenChecks <= 0f || elapsedTime >= timeBetweenChecks))
 54                {
 55                    //TODO(Brian): Remove later when we implement a centralized way of handling time budgets
 6956                    var messagingManager = Environment.i.messaging.manager as MessagingControllersManager;
 57
 58                    void processEntitiesList(HashSet<IDCLEntity> entities)
 59                    {
 6960                        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
 6967                        using HashSet<IDCLEntity>.Enumerator iterator = entities.GetEnumerator();
 14268                        while (iterator.MoveNext())
 69                        {
 7370                            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
 7377                            float startTime = Time.realtimeSinceStartup;
 78
 7379                            RunEntityEvaluation(iterator.Current);
 7380                            checkedEntities.Add(iterator.Current);
 81
 7382                            float finishTime = Time.realtimeSinceStartup;
 83
 7384                            if ( messagingManager != null )
 7385                                messagingManager.timeBudgetCounter -= (finishTime - startTime);
 86                        }
 13887                    }
 88
 6989                    processEntitiesList(entitiesToCheck);
 90
 91                    // As we can't modify the hashset while traversing it, we keep track of the entities that should be 
 6992                    using (var iterator = checkedEntities.GetEnumerator())
 93                    {
 14294                        while (iterator.MoveNext())
 95                        {
 7396                            RemoveEntity(iterator.Current, removeIfPersistent: false, resetState: false);
 97                        }
 6998                    }
 99
 100                    if(VERBOSE)
 101                        logger.Verbose($"Finished checking entities: checked entities {checkedEntities.Count}; entitiesT
 102
 69103                    checkedEntities.Clear();
 104
 69105                    lastCheckTime = Time.realtimeSinceStartup;
 106                }
 107
 7514108                yield return null;
 109            }
 110        }
 111
 112        public void Restart()
 113        {
 35114            Stop();
 35115            Start();
 35116        }
 117
 118        public void Start()
 119        {
 701120            if (entitiesCheckRoutine != null)
 0121                return;
 122
 701123            lastCheckTime = Time.realtimeSinceStartup;
 701124            entitiesCheckRoutine = CoroutineStarter.Start(CheckEntities());
 701125        }
 126
 127        public void Stop()
 128        {
 722129            if (entitiesCheckRoutine == null)
 21130                return;
 131
 701132            CoroutineStarter.Stop(entitiesCheckRoutine);
 701133            entitiesCheckRoutine = null;
 701134        }
 135
 136        public void Dispose()
 137        {
 666138            Stop();
 666139        }
 140
 141        public void AddEntityToBeChecked(IDCLEntity entity, bool isPersistent = false, bool runPreliminaryEvaluation = f
 142        {
 1261143            if (!enabled || (entity.scene != null && entity.scene.isPersistent))
 965144                return;
 145
 296146            if (runPreliminaryEvaluation)
 147            {
 148                // The outer bounds check is cheaper than the regular check
 234149                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.
 234153                if (!isPersistent && !entity.isInsideSceneOuterBoundaries)
 84154                    return;
 155            }
 156
 212157            entitiesToCheck.Add(entity);
 158
 212159            if (isPersistent)
 6160                persistentEntities.Add(entity);
 212161        }
 162
 163        public void RemoveEntity(IDCLEntity entity, bool removeIfPersistent = false, bool resetState = false)
 164        {
 537165            if (!enabled || (!removeIfPersistent && persistentEntities.Contains(entity)))
 0166                return;
 167
 537168            entitiesToCheck.Remove(entity);
 537169            persistentEntities.Remove(entity);
 170
 537171            if(resetState)
 462172                SetMeshesAndComponentsInsideBoundariesState(entity, true);
 537173        }
 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        {
 87180            RunEntityEvaluation(entity, false);
 87181        }
 182
 183        public void RunEntityEvaluation(IDCLEntity entity, bool onlyOuterBoundsCheck)
 184        {
 332185            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 
 325189            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
 200            // If it has a mesh we don't evaluate its position due to artists "pivot point sloppiness", we evaluate its 
 325201            if (HasMesh(entity))
 154202                EvaluateMeshBounds(entity, onlyOuterBoundsCheck);
 203            else
 171204                EvaluateEntityPosition(entity, onlyOuterBoundsCheck);
 171205        }
 206
 207        private void EvaluateMeshBounds(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 208        {
 209            // TODO: Can we cache the MaterialTransitionController somewhere to avoid this GetComponent() call?
 210            // If the mesh is being loaded we should skip the evaluation (it will be triggered again later when the load
 154211            if (entity.meshRootGameObject.GetComponent<MaterialTransitionController>()) // the object's MaterialTransiti
 0212                return;
 213
 154214            var loadWrapper = Environment.i.world.state.GetLoaderForEntity(entity);
 154215            if (loadWrapper != null && !loadWrapper.alreadyLoaded)
 8216                return;
 217
 146218            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entity.meshesInfo.mergedBoun
 219
 146220            if (!entity.isInsideSceneOuterBoundaries)
 93221                SetMeshesAndComponentsInsideBoundariesState(entity, false);
 222
 146223            if (!onlyOuterBoundsCheck)
 47224                SetMeshesAndComponentsInsideBoundariesState(entity, IsEntityMeshInsideSceneBoundaries(entity));
 146225        }
 226
 227        private void EvaluateEntityPosition(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 228        {
 171229            Vector3 entityGOPosition = entity.gameObject.transform.position;
 171230            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entityGOPosition);
 231
 171232            if (!entity.isInsideSceneOuterBoundaries)
 233            {
 33234                SetComponentsInsideBoundariesValidState(entity, false);
 33235                SetEntityInsideBoundariesState(entity, false);
 236            }
 237
 171238            if (!onlyOuterBoundsCheck)
 239            {
 37240                bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entityGOPosition + CommonScriptableObject
 37241                SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 37242                SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 243            }
 171244        }
 245
 246        private void SetEntityInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 247        {
 672248            if (entity.isInsideSceneBoundaries == isInsideBoundaries)
 552249                return;
 250
 120251            entity.isInsideSceneBoundaries = isInsideBoundaries;
 120252            OnEntityBoundsCheckerStatusChanged?.Invoke(entity, isInsideBoundaries);
 3253        }
 254
 255        private bool HasMesh(IDCLEntity entity)
 256        {
 325257            return entity.meshRootGameObject != null
 258                    && (entity.meshesInfo.colliders.Count > 0
 259                    || (entity.meshesInfo.renderers != null
 260                    && entity.meshesInfo.renderers.Length > 0));
 261        }
 262
 263        public bool IsEntityMeshInsideSceneBoundaries(IDCLEntity entity)
 264        {
 88265            if (entity.meshesInfo == null
 266                || entity.meshesInfo.meshRootGameObject == null
 267                || entity.meshesInfo.mergedBounds == null)
 25268                return false;
 269
 270            // 1st check (full mesh AABB)
 63271            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entity.meshesInfo.mergedBounds);
 272
 273            // 2nd check (submeshes & colliders AABB)
 63274            if (!isInsideBoundaries)
 275            {
 28276                isInsideBoundaries = AreSubmeshesInsideBoundaries(entity) && AreCollidersInsideBoundaries(entity);
 277            }
 278
 63279            return isInsideBoundaries;
 280        }
 281
 282        private bool AreSubmeshesInsideBoundaries(IDCLEntity entity)
 283        {
 66284            for (int i = 0; i < entity.meshesInfo.renderers.Length; i++)
 285            {
 29286                Renderer renderer = entity.meshesInfo.renderers[i];
 29287                if (renderer == null)
 288                    continue;
 289
 29290                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(renderer.bounds, renderer.transf
 24291                    return false;
 292            }
 293
 4294            return true;
 295        }
 296
 297        private bool AreCollidersInsideBoundaries(IDCLEntity entity)
 298        {
 13299            foreach (Collider collider in entity.meshesInfo.colliders)
 300            {
 4301                if (collider == null)
 302                    continue;
 303
 4304                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(collider.bounds, collider.transf
 3305                    return false;
 306            }
 307
 1308            return true;
 3309        }
 310
 311        private void SetMeshesAndComponentsInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 312        {
 602313            SetEntityMeshesInsideBoundariesState(entity.meshesInfo, isInsideBoundaries);
 602314            SetEntityCollidersInsideBoundariesState(entity.meshesInfo, isInsideBoundaries);
 602315            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 316
 317            // Should always be set last as entity.isInsideSceneBoundaries is checked to avoid re-running code unnecessa
 602318            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 602319        }
 320
 321        private void SetEntityMeshesInsideBoundariesState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 322        {
 602323            feedbackStyle.ApplyFeedback(meshesInfo, isInsideBoundaries);
 602324        }
 325
 326        private void SetEntityCollidersInsideBoundariesState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 327        {
 602328            if (meshesInfo == null || meshesInfo.colliders.Count == 0 || !meshesInfo.currentShape.HasCollisions())
 394329                return;
 330
 910331            foreach (Collider collider in meshesInfo.colliders)
 332            {
 247333                if (collider == null) continue;
 334
 247335                if (collider.enabled != isInsideBoundaries)
 128336                    collider.enabled = isInsideBoundaries;
 337            }
 208338        }
 339
 340        private void SetComponentsInsideBoundariesValidState(IDCLEntity entity, bool isInsideBoundaries)
 341        {
 672342            if(entity.isInsideSceneBoundaries == isInsideBoundaries || !DataStore.i.sceneBoundariesChecker.componentsChe
 670343                return;
 344
 8345            foreach (IOutOfSceneBoundariesHandler component in DataStore.i.sceneBoundariesChecker.componentsCheckSceneBo
 346            {
 2347                component.UpdateOutOfBoundariesState(isInsideBoundaries);
 348            }
 2349        }
 350    }
 351}