< 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:146
Uncovered lines:5
Coverable lines:151
Total lines:346
Line coverage:96.6% (146 of 151)
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%550100%
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;
 97512        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        {
 992450            while (true)
 51            {
 1062552                float elapsedTime = Time.realtimeSinceStartup - lastCheckTime;
 1062553                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
 10625108                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        {
 720129            if (entitiesCheckRoutine == null)
 19130                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        {
 1219143            if (!enabled || (entity.scene != null && entity.scene.isPersistent))
 923144                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        {
 154209            var loadWrapper = Environment.i.world.state.GetLoaderForEntity(entity);
 154210            if (loadWrapper != null && !loadWrapper.alreadyLoaded)
 8211                return;
 212
 146213            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entity.meshesInfo.mergedBoun
 214
 146215            if (!entity.isInsideSceneOuterBoundaries)
 93216                SetMeshesAndComponentsInsideBoundariesState(entity, false);
 217
 146218            if (!onlyOuterBoundsCheck)
 47219                SetMeshesAndComponentsInsideBoundariesState(entity, IsEntityMeshInsideSceneBoundaries(entity));
 146220        }
 221
 222        private void EvaluateEntityPosition(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 223        {
 171224            Vector3 entityGOPosition = entity.gameObject.transform.position;
 171225            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entityGOPosition);
 226
 171227            if (!entity.isInsideSceneOuterBoundaries)
 228            {
 33229                SetComponentsInsideBoundariesValidState(entity, false);
 33230                SetEntityInsideBoundariesState(entity, false);
 231            }
 232
 171233            if (!onlyOuterBoundsCheck)
 234            {
 37235                bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entityGOPosition + CommonScriptableObject
 37236                SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 37237                SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 238            }
 171239        }
 240
 241        private void SetEntityInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 242        {
 672243            if (entity.isInsideSceneBoundaries == isInsideBoundaries)
 552244                return;
 245
 120246            entity.isInsideSceneBoundaries = isInsideBoundaries;
 120247            OnEntityBoundsCheckerStatusChanged?.Invoke(entity, isInsideBoundaries);
 3248        }
 249
 250        private bool HasMesh(IDCLEntity entity)
 251        {
 325252            return entity.meshRootGameObject != null
 253                    && (entity.meshesInfo.colliders.Count > 0
 254                    || (entity.meshesInfo.renderers != null
 255                    && entity.meshesInfo.renderers.Length > 0));
 256        }
 257
 258        public bool IsEntityMeshInsideSceneBoundaries(IDCLEntity entity)
 259        {
 88260            if (entity.meshesInfo == null
 261                || entity.meshesInfo.meshRootGameObject == null
 262                || entity.meshesInfo.mergedBounds == null)
 25263                return false;
 264
 265            // 1st check (full mesh AABB)
 63266            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entity.meshesInfo.mergedBounds);
 267
 268            // 2nd check (submeshes & colliders AABB)
 63269            if (!isInsideBoundaries)
 270            {
 28271                isInsideBoundaries = AreSubmeshesInsideBoundaries(entity) && AreCollidersInsideBoundaries(entity);
 272            }
 273
 63274            return isInsideBoundaries;
 275        }
 276
 277        private bool AreSubmeshesInsideBoundaries(IDCLEntity entity)
 278        {
 66279            for (int i = 0; i < entity.meshesInfo.renderers.Length; i++)
 280            {
 29281                Renderer renderer = entity.meshesInfo.renderers[i];
 29282                if (renderer == null)
 283                    continue;
 284
 29285                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(renderer.bounds, renderer.transf
 24286                    return false;
 287            }
 288
 4289            return true;
 290        }
 291
 292        private bool AreCollidersInsideBoundaries(IDCLEntity entity)
 293        {
 13294            foreach (Collider collider in entity.meshesInfo.colliders)
 295            {
 4296                if (collider == null)
 297                    continue;
 298
 4299                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(collider.bounds, collider.transf
 3300                    return false;
 301            }
 302
 1303            return true;
 3304        }
 305
 306        private void SetMeshesAndComponentsInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 307        {
 602308            SetEntityMeshesInsideBoundariesState(entity.meshesInfo, isInsideBoundaries);
 602309            SetEntityCollidersInsideBoundariesState(entity.meshesInfo, isInsideBoundaries);
 602310            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 311
 312            // Should always be set last as entity.isInsideSceneBoundaries is checked to avoid re-running code unnecessa
 602313            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 602314        }
 315
 316        private void SetEntityMeshesInsideBoundariesState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 317        {
 602318            feedbackStyle.ApplyFeedback(meshesInfo, isInsideBoundaries);
 602319        }
 320
 321        private void SetEntityCollidersInsideBoundariesState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 322        {
 602323            if (meshesInfo == null || meshesInfo.colliders.Count == 0 || !meshesInfo.currentShape.HasCollisions())
 394324                return;
 325
 910326            foreach (Collider collider in meshesInfo.colliders)
 327            {
 247328                if (collider == null) continue;
 329
 247330                if (collider.enabled != isInsideBoundaries)
 128331                    collider.enabled = isInsideBoundaries;
 332            }
 208333        }
 334
 335        private void SetComponentsInsideBoundariesValidState(IDCLEntity entity, bool isInsideBoundaries)
 336        {
 672337            if(entity.isInsideSceneBoundaries == isInsideBoundaries || !DataStore.i.sceneBoundariesChecker.componentsChe
 670338                return;
 339
 8340            foreach (IOutOfSceneBoundariesHandler component in DataStore.i.sceneBoundariesChecker.componentsCheckSceneBo
 341            {
 2342                component.UpdateOutOfBoundariesState(isInsideBoundaries);
 343            }
 2344        }
 345    }
 346}