< 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%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%
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;
 275712        public bool enabled => entitiesCheckRoutine != null;
 105513        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        {
 3159550            while (true)
 51            {
 3229552                float elapsedTime = Time.realtimeSinceStartup - lastCheckTime;
 3229553                if ((entitiesToCheck.Count > 0) && (timeBetweenChecks <= 0f || elapsedTime >= timeBetweenChecks))
 54                {
 55                    //TODO(Brian): Remove later when we implement a centralized way of handling time budgets
 6456                    var messagingManager = Environment.i.messaging.manager as MessagingControllersManager;
 57
 58                    void processEntitiesList(HashSet<IDCLEntity> entities)
 59                    {
 6460                        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
 6467                        using HashSet<IDCLEntity>.Enumerator iterator = entities.GetEnumerator();
 12968                        while (iterator.MoveNext())
 69                        {
 6570                            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
 6577                            float startTime = Time.realtimeSinceStartup;
 78
 6579                            RunEntityEvaluation(iterator.Current);
 6580                            checkedEntities.Add(iterator.Current);
 81
 6582                            float finishTime = Time.realtimeSinceStartup;
 83
 6584                            if ( messagingManager != null )
 6585                                messagingManager.timeBudgetCounter -= (finishTime - startTime);
 86                        }
 12887                    }
 88
 6489                    processEntitiesList(entitiesToCheck);
 90
 91                    // As we can't modify the hashset while traversing it, we keep track of the entities that should be 
 6492                    using (var iterator = checkedEntities.GetEnumerator())
 93                    {
 12994                        while (iterator.MoveNext())
 95                        {
 6596                            RemoveEntity(iterator.Current, removeIfPersistent: false, resetState: false);
 97                        }
 6498                    }
 99
 100                    if(VERBOSE)
 101                        logger.Verbose($"Finished checking entities: checked entities {checkedEntities.Count}; entitiesT
 102
 64103                    checkedEntities.Clear();
 104
 64105                    lastCheckTime = Time.realtimeSinceStartup;
 106                }
 107
 32295108                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 || (entity.scene != null && entity.scene.isPersistent))
 950144                return;
 145
 286146            if (runPreliminaryEvaluation)
 147            {
 148                // The outer bounds check is cheaper than the regular check
 230149                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.
 230153                if (!isPersistent && !entity.isInsideSceneOuterBoundaries)
 82154                    return;
 155            }
 156
 204157            entitiesToCheck.Add(entity);
 158
 204159            if (isPersistent)
 6160                persistentEntities.Add(entity);
 204161        }
 162
 163        public void RemoveEntity(IDCLEntity entity, bool removeIfPersistent = false, bool resetState = false)
 164        {
 528165            if (!enabled || (!removeIfPersistent && persistentEntities.Contains(entity)))
 0166                return;
 167
 528168            entitiesToCheck.Remove(entity);
 528169            persistentEntities.Remove(entity);
 170
 528171            if(resetState)
 461172                SetMeshesAndComponentsInsideBoundariesState(entity, true);
 528173        }
 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        {
 79180            RunEntityEvaluation(entity, false);
 79181        }
 182
 183        public void RunEntityEvaluation(IDCLEntity entity, bool onlyOuterBoundsCheck)
 184        {
 320185            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 
 313189            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 
 313201            if (HasMesh(entity))
 146202                EvaluateMeshBounds(entity, onlyOuterBoundsCheck);
 203            else
 167204                EvaluateEntityPosition(entity, onlyOuterBoundsCheck);
 167205        }
 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
 146211            if (entity.meshRootGameObject.GetComponent<MaterialTransitionController>()) // the object's MaterialTransiti
 0212                return;
 213
 146214            var loadWrapper = Environment.i.world.state.GetLoaderForEntity(entity);
 146215            if (loadWrapper != null && !loadWrapper.alreadyLoaded)
 8216                return;
 217
 138218            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entity.meshesInfo.mergedBoun
 219
 138220            if (!entity.isInsideSceneOuterBoundaries)
 87221                SetMeshesAndComponentsInsideBoundariesState(entity, false);
 222
 138223            if (!onlyOuterBoundsCheck)
 42224                SetMeshesAndComponentsInsideBoundariesState(entity, IsEntityMeshInsideSceneBoundaries(entity));
 138225        }
 226
 227        private void EvaluateEntityPosition(IDCLEntity entity, bool onlyOuterBoundsCheck = false)
 228        {
 167229            Vector3 entityGOPosition = entity.gameObject.transform.position;
 167230            entity.isInsideSceneOuterBoundaries = entity.scene.IsInsideSceneOuterBoundaries(entityGOPosition);
 231
 167232            if (!entity.isInsideSceneOuterBoundaries)
 233            {
 32234                SetEntityInsideBoundariesState(entity, false);
 32235                SetComponentsInsideBoundariesValidState(entity, false);
 236            }
 237
 167238            if (!onlyOuterBoundsCheck)
 239            {
 34240                bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entityGOPosition + CommonScriptableObject
 34241                SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 34242                SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 243            }
 167244        }
 245
 246        private void SetEntityInsideBoundariesState(IDCLEntity entity, bool isInsideBoundaries)
 247        {
 656248            if (entity.isInsideSceneBoundaries == isInsideBoundaries)
 540249                return;
 250
 116251            entity.isInsideSceneBoundaries = isInsideBoundaries;
 116252            OnEntityBoundsCheckerStatusChanged?.Invoke(entity, isInsideBoundaries);
 3253        }
 254
 255        private bool HasMesh(IDCLEntity entity)
 256        {
 313257            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        {
 83265            if (entity.meshesInfo == null
 266                || entity.meshesInfo.meshRootGameObject == null
 267                || entity.meshesInfo.mergedBounds == null)
 25268                return false;
 269
 270            // 1st check (full mesh AABB)
 58271            bool isInsideBoundaries = entity.scene.IsInsideSceneBoundaries(entity.meshesInfo.mergedBounds);
 272
 273            // 2nd check (submeshes & colliders AABB)
 58274            if (!isInsideBoundaries)
 275            {
 24276                isInsideBoundaries = AreSubmeshesInsideBoundaries(entity) && AreCollidersInsideBoundaries(entity);
 277            }
 278
 58279            return isInsideBoundaries;
 280        }
 281
 282        private bool AreSubmeshesInsideBoundaries(IDCLEntity entity)
 283        {
 58284            for (int i = 0; i < entity.meshesInfo.renderers.Length; i++)
 285            {
 25286                Renderer renderer = entity.meshesInfo.renderers[i];
 25287                if (renderer == null)
 288                    continue;
 289
 25290                if (!entity.scene.IsInsideSceneBoundaries(MeshesInfoUtils.GetSafeBounds(renderer.bounds, renderer.transf
 20291                    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        {
 590313            SetEntityInsideBoundariesState(entity, isInsideBoundaries);
 314
 590315            SetEntityMeshesValidState(entity.meshesInfo, isInsideBoundaries);
 590316            SetEntityCollidersValidState(entity.meshesInfo, isInsideBoundaries);
 590317            SetComponentsInsideBoundariesValidState(entity, isInsideBoundaries);
 590318        }
 319
 320        private void SetEntityMeshesValidState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 321        {
 590322            feedbackStyle.ApplyFeedback(meshesInfo, isInsideBoundaries);
 590323        }
 324
 325        private void SetEntityCollidersValidState(MeshesInfo meshesInfo, bool isInsideBoundaries)
 326        {
 590327            if (meshesInfo == null || meshesInfo.colliders.Count == 0 || !meshesInfo.currentShape.HasCollisions())
 394328                return;
 329
 862330            foreach (Collider collider in meshesInfo.colliders)
 331            {
 235332                if (collider == null) continue;
 333
 235334                if (collider.enabled != isInsideBoundaries)
 126335                    collider.enabled = isInsideBoundaries;
 336            }
 196337        }
 338
 339        private void SetComponentsInsideBoundariesValidState(IDCLEntity entity, bool isInsideBoundaries)
 340        {
 656341            if(!DataStore.i.sceneBoundariesChecker.componentsCheckSceneBoundaries.ContainsKey(entity.entityId))
 648342                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}