< 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:148
Uncovered lines:6
Coverable lines:154
Total lines:352
Line coverage:96.1% (148 of 154)
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%660100%
RemoveEntity(...)0%5.075085.71%
WasAddedAsPersistent(...)0%110100%
RunEntityEvaluation(...)0%110100%
RunEntityEvaluation(...)0%7.047090.91%
EvaluateMeshBounds(...)0%660100%
EvaluateEntityPosition(...)0%330100%
SetEntityInsideBoundariesState(...)0%330100%
HasMesh(...)0%440100%
IsEntityMeshInsideSceneBoundaries(...)0%550100%
AreSubmeshesInsideBoundaries(...)0%440100%
AreCollidersInsideBoundaries(...)0%440100%
SetMeshesAndComponentsInsideBoundariesState(...)0%110100%
SetEntityMeshesValidState(...)0%110100%
SetEntityCollidersValidState(...)0%770100%
SetComponentsInsideBoundariesValidState(...)0%330100%

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;
 99312        public bool enabled => entitiesCheckRoutine != null;
 70013        public float timeBetweenChecks { get; set; } = 0.5f;
 214        public int entitiesToCheckCount => entitiesToCheck.Count;
 15
 16        private const bool VERBOSE = false;
 66517        private Logger logger = new Logger("SceneBoundsChecker") {verboseEnabled = VERBOSE};
 66518        private HashSet<IDCLEntity> entitiesToCheck = new HashSet<IDCLEntity>();
 66519        private HashSet<IDCLEntity> checkedEntities = new HashSet<IDCLEntity>();
 66520        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        {
 66527            Start();
 66528        }
 29
 66530        public SceneBoundsChecker(ISceneBoundsFeedbackStyle feedbackStyle = null)
 31        {
 66532            this.feedbackStyle = feedbackStyle ?? new SceneBoundsFeedbackStyle_Simple();
 66533        }
 34
 35        public void SetFeedbackStyle(ISceneBoundsFeedbackStyle feedbackStyle)
 36        {
 3537            this.feedbackStyle.CleanFeedback();
 3538            this.feedbackStyle = feedbackStyle;
 3539            Restart();
 3540        }
 41
 6942        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        {
 680050            while (true)
 51            {
 750052                float elapsedTime = Time.realtimeSinceStartup - lastCheckTime;
 750053                if ((entitiesToCheck.Count > 0) && (timeBetweenChecks <= 0f || elapsedTime >= timeBetweenChecks))
 54                {
 55                    //TODO(Brian): Remove later when we implement a centralized way of handling time budgets
 7356                    var messagingManager = Environment.i.messaging.manager as MessagingControllersManager;
 57
 58                    void processEntitiesList(HashSet<IDCLEntity> entities)
 59                    {
 7360                        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
 7367                        using HashSet<IDCLEntity>.Enumerator iterator = entities.GetEnumerator();
 15068                        while (iterator.MoveNext())
 69                        {
 7770                            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
 7777                            float startTime = Time.realtimeSinceStartup;
 78
 7779                            RunEntityEvaluation(iterator.Current);
 7780                            checkedEntities.Add(iterator.Current);
 81
 7782                            float finishTime = Time.realtimeSinceStartup;
 83
 7784                            if ( messagingManager != null )
 7785                                messagingManager.timeBudgetCounter -= (finishTime - startTime);
 86                        }
 14687                    }
 88
 7389                    processEntitiesList(entitiesToCheck);
 90
 91                    // As we can't modify the hashset while traversing it, we keep track of the entities that should be 
 7392                    using (var iterator = checkedEntities.GetEnumerator())
 93                    {
 15094                        while (iterator.MoveNext())
 95                        {
 7796                            RemoveEntity(iterator.Current, removeIfPersistent: false, resetState: false);
 97                        }
 7398                    }
 99
 100                    if(VERBOSE)
 101                        logger.Verbose($"Finished checking entities: checked entities {checkedEntities.Count}; entitiesT
 102
 73103                    checkedEntities.Clear();
 104
 73105                    lastCheckTime = Time.realtimeSinceStartup;
 106                }
 107
 7500108                yield return null;
 109            }
 110        }
 111
 112        public void Restart()
 113        {
 35114            Stop();
 35115            Start();
 35116        }
 117
 118        public void Start()
 119        {
 700120            if (entitiesCheckRoutine != null)
 0121                return;
 122
 700123            lastCheckTime = Time.realtimeSinceStartup;
 700124            entitiesCheckRoutine = CoroutineStarter.Start(CheckEntities());
 700125        }
 126
 127        public void Stop()
 128        {
 721129            if (entitiesCheckRoutine == null)
 21130                return;
 131
 700132            CoroutineStarter.Stop(entitiesCheckRoutine);
 700133            entitiesCheckRoutine = null;
 700134        }
 135
 136        public void Dispose()
 137        {
 665138            Stop();
 665139        }
 140
 141        public void AddEntityToBeChecked(IDCLEntity entity, bool isPersistent = false, bool runPreliminaryEvaluation = f
 142        {
 1236143            if (!enabled)
 160144                return;
 145
 1076146            if (runPreliminaryEvaluation)
 147            {
 148                // The outer bounds check is cheaper than the regular check
 863149                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.
 863153                if (!isPersistent && !entity.isInsideSceneOuterBoundaries)
 166154                    return;
 155            }
 156
 910157            entitiesToCheck.Add(entity);
 158
 910159            if (isPersistent)
 38160                persistentEntities.Add(entity);
 910161        }
 162
 163        public void RemoveEntity(IDCLEntity entity, bool removeIfPersistent = false, bool resetState = false)
 164        {
 540165            if (!enabled || (!removeIfPersistent && persistentEntities.Contains(entity)))
 0166                return;
 167
 540168            entitiesToCheck.Remove(entity);
 540169            persistentEntities.Remove(entity);
 170
 540171            if(resetState)
 461172                SetMeshesAndComponentsInsideBoundariesState(entity, true);
 540173        }
 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        {
 91180            RunEntityEvaluation(entity, false);
 91181        }
 182
 183        public void RunEntityEvaluation(IDCLEntity entity, bool onlyOuterBoundsCheck)
 184        {
 965185            if (entity == null || entity.scene == null || entity.gameObject == null)
 0186                return;
 187
 188            // Recursively evaluate entity children as well, we need to check this up front because this entity may not 
 965189            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 
 965201            if (HasMesh(entity))
 325202                EvaluateMeshBounds(entity, onlyOuterBoundsCheck);
 203            else
 640204                EvaluateEntityPosition(entity, onlyOuterBoundsCheck);
 640205        }
 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
 325211            if (entity.meshRootGameObject.GetComponent<MaterialTransitionController>()) // the object's MaterialTransiti
 2212                return;
 213
 323214            var loadWrapper = Environment.i.world.state.GetLoaderForEntity(entity);
 323215            if (loadWrapper != null && !loadWrapper.alreadyLoaded)
 10216                return;
 217
 313218            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entity.meshesInfo.mergedBoun
 219
 313220            if (!entity.isInsideSceneOuterBoundaries)
 184221                SetMeshesAndComponentsInsideBoundariesState(entity, false);
 222
 313223            if (!onlyOuterBoundsCheck)
 48224                SetMeshesAndComponentsInsideBoundariesState(entity, IsEntityMeshInsideSceneBoundaries(entity));
 313225        }
 226
 227        private void EvaluateEntityPosition(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 228        {
 640229            Vector3 entityGOPosition = entity.gameObject.transform.position;
 640230            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entityGOPosition);
 231
 640232            if (!entity.isInsideSceneOuterBoundaries)
 233            {
 36234                SetEntityInsideBoundariesState(entity, false);
 36235                SetComponentsInsideBoundariesValidState(entity, false);
 236            }
 237
 640238            if (!onlyOuterBoundsCheck)
 239            {
 47240                bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entityGOPosition + CommonScriptableObject
 47241                SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 47242                SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 243            }
 640244        }
 245
 246        private void SetEntityInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 247        {
 776248            if (entity.isInsideSceneBoundaries == isInsideBoundaries)
 548249                return;
 250
 228251            entity.isInsideSceneBoundaries = isInsideBoundaries;
 228252            OnEntityBoundsCheckerStatusChanged?.Invoke(entity, isInsideBoundaries);
 4253        }
 254
 255        private bool HasMesh(IDCLEntity entity)
 256        {
 965257            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        {
 89265            if (entity.meshesInfo == null
 266                || entity.meshesInfo.meshRootGameObject == null
 267                || entity.meshesInfo.mergedBounds == null)
 25268                return false;
 269
 270            // 1st check (full mesh AABB)
 64271            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entity.meshesInfo.mergedBounds);
 272
 273            // 2nd check (submeshes & colliders AABB)
 64274            if (!isInsideBoundaries)
 275            {
 40276                isInsideBoundaries = AreSubmeshesInsideBoundaries(entity) && AreCollidersInsideBoundaries(entity);
 277            }
 278
 64279            return isInsideBoundaries;
 280        }
 281
 282        private bool AreSubmeshesInsideBoundaries(IDCLEntity entity)
 283        {
 90284            for (int i = 0; i < entity.meshesInfo.renderers.Length; i++)
 285            {
 41286                Renderer renderer = entity.meshesInfo.renderers[i];
 41287                if (renderer == null)
 288                    continue;
 289
 41290                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(renderer.bounds, renderer.transf
 36291                    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        {
 693313            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 314
 693315            SetEntityMeshesValidState(entity.meshesInfo, isInsideBoundaries);
 693316            SetEntityCollidersValidState(entity.meshesInfo, isInsideBoundaries);
 693317            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 693318        }
 319
 320        private void SetEntityMeshesValidState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 321        {
 693322            feedbackStyle.ApplyFeedback(meshesInfo, isInsideBoundaries);
 693323        }
 324
 325        private void SetEntityCollidersValidState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 326        {
 693327            if (meshesInfo == null || meshesInfo.colliders.Count == 0 || !meshesInfo.currentShape.HasCollisions())
 453328                return;
 329
 1038330            foreach (Collider collider in meshesInfo.colliders)
 331            {
 279332                if (collider == null) continue;
 333
 279334                if (collider.enabled != isInsideBoundaries)
 174335                    collider.enabled = isInsideBoundaries;
 336            }
 240337        }
 338
 339        private void SetComponentsInsideBoundariesValidState(IDCLEntity entity, bool isInsideBoundaries)
 340        {
 776341            if(!DataStore.i.sceneBoundariesChecker.componentsCheckSceneBoundaries.ContainsKey(entity.entityId))
 768342                return;
 343
 8344            List<IOutOfSceneBoundariesHandler> components = DataStore.i.sceneBoundariesChecker.componentsCheckSceneBound
 345
 32346            for (int i = 0; i < components.Count; i++)
 347            {
 8348                components[i].UpdateOutOfBoundariesState(isInsideBoundaries);
 349            }
 8350        }
 351    }
 352}