< Summary

Class:GDX.Developer.Reports.ResourcesAuditReport
Assembly:GDX
File(s):D:/BuildAgent/work/GDX-Documentation/Projects/GDX_Development/Packages/com.dotbunny.gdx/GDX/Developer/Reports/ResourcesAuditReport.cs
Covered lines:118
Uncovered lines:24
Coverable lines:142
Total lines:384
Line coverage:83% (118 of 142)
Covered branches:0
Total branches:0
Covered methods:9
Total methods:10
Method coverage:90% (9 of 10)

Coverage History

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ResourcesAuditReport()0%110100%
Finalize()0%110100%
Output(...)0%550100%
Query(...)0%5.235078.95%
QueryForType[TType, TObjectInfo](...)0%7.527078.05%
Remove(...)0%12300%
Get(...)0%220100%
GetAll()0%110100%
GetCommon()0%110100%
ResourcesQuery(...)0%110100%

File(s)

D:/BuildAgent/work/GDX-Documentation/Projects/GDX_Development/Packages/com.dotbunny.gdx/GDX/Developer/Reports/ResourcesAuditReport.cs

#LineLine coverage
 1// Copyright (c) 2020-2022 dotBunny Inc.
 2// dotBunny licenses this file to you under the BSL-1.0 license.
 3// See the LICENSE file in the project root for more information.
 4
 5#if !UNITY_DOTSRUNTIME
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Reflection;
 10using System.Text;
 11using GDX.Developer.Reports.Resource;
 12using GDX.Developer.Reports.Resource.Objects;
 13using GDX.Developer.Reports.Resource.Sections;
 14using UnityEngine;
 15using UnityEngine.Profiling;
 16using Object = UnityEngine.Object;
 17
 18namespace GDX.Developer.Reports
 19{
 20    /// <summary>
 21    ///     An audit of loaded <see cref="UnityEngine.Object" /> for queried types.
 22    /// </summary>
 23    /// <remarks>
 24    ///     Information is referenced to the target objects by a modified weak reference (<see cref="TransientReference"
 25    ///     thus this will not prevent garbage collection.
 26    /// </remarks>
 27    /// <exception cref="UnsupportedRuntimeException">Not supported on DOTS Runtime.</exception>
 28    public class ResourcesAuditReport : ResourceReport
 29    {
 630        public readonly ApplicationSection ApplicationContext = ApplicationSection.Get();
 31
 32        /// <summary>
 33        ///     A collection of known (loaded in memory) <see cref="UnityEngine.Object" /> keyed by type.
 34        /// </summary>
 635        public readonly Dictionary<Type, Dictionary<TransientReference, ObjectInfo>> KnownObjects =
 36            new Dictionary<Type, Dictionary<TransientReference, ObjectInfo>>();
 37
 38        /// <summary>
 39        ///     A collection of known <see cref="UnityEngine.Object" /> types in memory and their total usage.
 40        /// </summary>
 641        public readonly Dictionary<Type, long> KnownUsage = new Dictionary<Type, long>();
 42
 643        public readonly MemorySection MemoryContext = MemorySection.Get();
 44
 45        /// <summary>
 46        ///     The last time that the <see cref="ResourcesAuditReport" /> has had a query of types.
 47        /// </summary>
 648        public DateTime LastTouched = DateTime.Now;
 49
 50        /// <summary>
 51        ///     The total number of objects which are known to the <see cref="ResourcesAuditReport" />.
 52        /// </summary>
 53        public int ObjectCount;
 54
 55        /// <summary>
 56        ///     Process to destroy a <see cref="ResourcesAuditReport" />.
 57        /// </summary>
 58        ~ResourcesAuditReport()
 1259        {
 660            KnownObjects.Clear();
 1261        }
 62
 63        /// <inheritdoc />
 64        public override bool Output(StringBuilder builder, ResourceReportContext context = null)
 465        {
 66            // We need to make the context if its not provided
 467            context ??= new ResourceReportContext();
 68
 69            // Create header
 470            builder.AppendLine(CreateHeader(context,"START: Resources Audit Report"));
 71
 72            // Add standard report information
 473            ApplicationContext.Output(context, builder);
 74
 75            // Custom header information
 476            builder.AppendLine(CreateKeyValuePair(context,"Last Touched",
 77                LastTouched.ToString(Localization.LocalTimestampFormat)));
 478            builder.AppendLine(CreateKeyValuePair(context,"Total Objects", ObjectCount.ToString()));
 79
 480            builder.AppendLine();
 81
 82            // Add memory information
 483            MemoryContext.Output(context, builder);
 484            builder.AppendLine();
 85
 86            // We iterate over each defined type in the order they were added to the known objects
 8087            foreach (KeyValuePair<Type, Dictionary<TransientReference, ObjectInfo>> knownObject in KnownObjects)
 3488            {
 3489                int count = knownObject.Value.Count;
 90
 3491                builder.AppendLine(CreateHeader(context, knownObject.Key.ToString(), '-'));
 3492                builder.AppendLine(CreateKeyValuePair(context,"Count", count.ToString()));
 3493                builder.AppendLine(CreateKeyValuePair(context,"Total Size",
 94                    Localization.GetHumanReadableFileSize(KnownUsage[knownObject.Key])));
 3495                builder.AppendLine();
 96
 97                // Sort the known objects based on size as that's the most useful context to have them listed
 3498                List<ObjectInfo> newList = new List<ObjectInfo>(count);
 2018899                foreach (KeyValuePair<TransientReference, ObjectInfo> objectInfo in knownObject.Value)
 10043100                {
 10043101                    newList.Add(objectInfo.Value);
 10043102                }
 103
 34104                newList.Sort();
 105
 106                // Output each item
 20154107                for (int i = 0; i < count; i++)
 10043108                {
 10043109                    newList[i].Output(context, builder);
 10043110                }
 111
 34112                builder.AppendLine();
 34113            }
 114
 115            // Footer
 4116            builder.AppendLine(CreateHeader(context,"END: Resources Audit Report"));
 117
 4118            return true;
 4119        }
 120
 121        /// <summary>
 122        ///     Identify loaded <see cref="UnityEngine.Object" /> of the description provided in <paramref cref="query" 
 123        /// </summary>
 124        /// <remarks>
 125        ///     This method of querying uses reflection to allow for dynamic developer console calls,
 126        ///     <see cref="QueryForType{TType,TObjectInfo}" /> for a much faster typed operation.
 127        /// </remarks>
 128        /// <param name="query">Description of <see cref="UnityEngine.Object" /> type to search for.</param>
 129        public void Query(ResourcesQuery query)
 2130        {
 131            // Attempt to try and make a type based on the full name (+namespace), and the assembly.
 132            // ex: UnityEngine.Texture2D,UnityEngine
 2133            Type typeActual = Type.GetType(query.TypeDefinition, false);
 134
 135            // If we actually got a valid type
 2136            if (typeActual == null)
 0137            {
 0138                return;
 139            }
 140
 141            // Create our ObjectInfo type, defaulting if invalid
 2142            Type objectInfoActual = null;
 2143            if (query.ObjectInfoTypeDefinition != null)
 1144            {
 1145                objectInfoActual = Type.GetType(query.ObjectInfoTypeDefinition, false);
 1146            }
 147
 2148            objectInfoActual ??= ObjectInfoFactory.GetObjectInfoType(typeActual);
 149
 150
 151            // Build out using reflection (yes bad, but you choose this).
 2152            MethodInfo method = typeof(ResourcesAuditReport).GetMethod(nameof(QueryForType));
 153
 154            // Did we find the method?
 2155            if (method is null)
 0156            {
 0157                return;
 158            }
 159
 2160            MethodInfo generic = method.MakeGenericMethod(typeActual, objectInfoActual);
 161
 162            // Invoke the method on our container
 2163            generic.Invoke(this, new object[] {query.NameFilter});
 164
 2165            LastTouched = DateTime.Now;
 2166        }
 167
 168        /// <summary>
 169        ///     Identify loaded <typeparamref name="TType" />, using <typeparamref name="TObjectInfo" /> for report gene
 170        /// </summary>
 171        /// <typeparam name="TType">The object type to query for.</typeparam>
 172        /// <typeparam name="TObjectInfo">The <see cref="ObjectInfo" /> used to generate report entries.</typeparam>
 173        public void QueryForType<TType, TObjectInfo>(string nameFilter = null)
 174            where TType : Object
 175            where TObjectInfo : ObjectInfo, new()
 37176        {
 177            // Find any matching resources
 37178            TType[] foundLoadedObjects = Resources.FindObjectsOfTypeAll<TType>();
 37179            Type typeClass = typeof(TType);
 180
 181            // Make sure the category exists
 37182            if (!KnownObjects.ContainsKey(typeClass))
 37183            {
 37184                KnownObjects.Add(typeClass, new Dictionary<TransientReference, ObjectInfo>());
 37185            }
 186
 37187            if (!KnownUsage.ContainsKey(typeClass))
 37188            {
 37189                KnownUsage.Add(typeClass, 0);
 37190            }
 191
 192            // Get reference to the dictionary for the specified category
 37193            Dictionary<TransientReference, ObjectInfo> typeObjects = KnownObjects[typeClass];
 194
 37195            bool evaluateNames = !string.IsNullOrEmpty(nameFilter);
 37196            int count = foundLoadedObjects.Length;
 36420197            for (int i = 0; i < count; i++)
 18173198            {
 18173199                Object foundObject = foundLoadedObjects[i];
 200
 201                // If we have a provided filter, and our objects name doesnt contain the filter, skip
 18173202                if (evaluateNames && !foundObject.name.Contains(nameFilter))
 0203                {
 0204                    continue;
 205                }
 206
 18173207                TransientReference pseudoWeakReference = new TransientReference(foundObject);
 208
 209                // We can use the hashcode of a specific type at this level to determine duplication
 18173210                if (typeObjects.ContainsKey(pseudoWeakReference))
 0211                {
 0212                    ObjectInfo foundObjectInfo = typeObjects[pseudoWeakReference];
 213
 214                    // Increment copy count
 0215                    foundObjectInfo.CopyCount++;
 216
 217                    // We actively not going to use the existing size, in case the copy is different.
 0218                    long usage = Profiler.GetRuntimeMemorySizeLong(foundObject);
 0219                    foundObjectInfo.TotalMemoryUsage += usage;
 0220                    KnownUsage[typeClass] += usage;
 0221                }
 222                else
 18173223                {
 18173224                    TObjectInfo objectInfo = new TObjectInfo();
 18173225                    objectInfo.Populate(foundObject, pseudoWeakReference);
 18173226                    typeObjects.Add(pseudoWeakReference, objectInfo);
 227
 228                    // Add to size
 18173229                    KnownUsage[typeClass] += objectInfo.MemoryUsage;
 18173230                    ObjectCount++;
 18173231                }
 18173232            }
 233
 37234            LastTouched = DateTime.Now;
 37235        }
 236
 237        /// <summary>
 238        ///     Remove all information regarding a specific <paramref name="type" /> from the <see cref="KnownObjects" /
 239        /// </summary>
 240        /// <param name="type">The type to remove from the <see cref="KnownObjects" />.</param>
 241        public void Remove(Type type)
 0242        {
 0243            if (KnownObjects.ContainsKey(type))
 0244            {
 245                // Decrement the object count
 0246                ObjectCount -= KnownObjects.Count;
 247
 248                // Remove the known objects type
 0249                KnownObjects.Remove(type);
 0250            }
 251
 252            // Remove the size data
 0253            if (KnownUsage.ContainsKey(type))
 0254            {
 0255                KnownUsage.Remove(type);
 0256            }
 0257        }
 258
 259        /// <summary>
 260        ///     Generate a <see cref="ResourcesAuditReport" /> for the provided <see cref="ResourcesQuery" /> array.
 261        /// </summary>
 262        /// <remarks>
 263        ///     This method uses reflection to generate the necessary typed parameters, its often more efficient to
 264        ///     create your own custom reports like in <see cref="GetCommon" />.
 265        /// </remarks>
 266        /// <param name="queries">A list of <see cref="ResourcesQuery" /> to generate a report from.</param>
 267        /// <returns>A <see cref="ResourcesAuditReport" /> containing the outlined types.</returns>
 268        public static ResourcesAuditReport Get(ResourcesQuery[] queries)
 1269        {
 270            // Create our collection object, this is going to effect memory based on its size
 1271            ResourcesAuditReport resourcesAuditReport = new ResourcesAuditReport();
 272
 273            // Types to actual?
 1274            int count = queries.Length;
 6275            for (int i = 0; i < count; i++)
 2276            {
 2277                resourcesAuditReport.Query(queries[i]);
 2278            }
 279
 1280            return resourcesAuditReport;
 1281        }
 282
 283        /// <summary>
 284        ///     Get a <see cref="ResourcesAuditReport" /> of all <see cref="UnityEngine.Object" />.
 285        /// </summary>
 286        /// <returns>A <see cref="ResourcesAuditReport" /> of all objects.</returns>
 287        public static ResourcesAuditReport GetAll()
 2288        {
 2289            ResourcesAuditReport resourcesAuditReport = new ResourcesAuditReport();
 2290            resourcesAuditReport.QueryForType<Object, ObjectInfo>();
 2291            return resourcesAuditReport;
 2292        }
 293
 294        /// <summary>
 295        ///     Get a <see cref="ResourcesAuditReport" /> of a common subset of data which usually eats a large portion 
 296        ///     memory, and often can reveal memory leaks.
 297        /// </summary>
 298        /// <returns>A <see cref="ResourcesAuditReport" /> of textures, shaders, materials and animations.</returns>
 299        public static ResourcesAuditReport GetCommon()
 3300        {
 301            // Create our collection object, this is going to effect memory based on its size
 3302            ResourcesAuditReport resourcesAuditReport = new ResourcesAuditReport();
 303
 304            // Sections
 3305            resourcesAuditReport.QueryForType<RenderTexture, TextureObjectInfo>();
 3306            resourcesAuditReport.QueryForType<Texture3D, TextureObjectInfo>();
 3307            resourcesAuditReport.QueryForType<Texture2D, TextureObjectInfo>();
 3308            resourcesAuditReport.QueryForType<Texture2DArray, TextureObjectInfo>();
 3309            resourcesAuditReport.QueryForType<Cubemap, TextureObjectInfo>();
 3310            resourcesAuditReport.QueryForType<CubemapArray, TextureObjectInfo>();
 3311            resourcesAuditReport.QueryForType<Shader, ShaderObjectInfo>();
 3312            resourcesAuditReport.QueryForType<Material, ObjectInfo>();
 3313            resourcesAuditReport.QueryForType<Mesh, MeshObjectInfo>();
 3314            resourcesAuditReport.QueryForType<AnimationClip, ObjectInfo>();
 3315            resourcesAuditReport.QueryForType<AssetBundle, AssetBundleObjectInfo>();
 316
 3317            return resourcesAuditReport;
 3318        }
 319
 320        /// <summary>
 321        ///     A structure that defines the string inputs necessary to query for loaded resources of a specific type.
 322        /// </summary>
 323        /// <remarks>
 324        ///     This forces the path through reflection when querying; there are faster methods available. These queries
 325        ///     built ideally to support dynamic developer console calls.
 326        /// </remarks>
 327        public readonly struct ResourcesQuery
 328        {
 329            /// <summary>
 330            ///     The fully qualified type that is going to be evaluated.
 331            /// </summary>
 332            /// <example>
 333            ///     UnityEngine.Texture2D,UnityEngine
 334            /// </example>
 335            public readonly string TypeDefinition;
 336
 337            /// <summary>
 338            ///     The fully qualified type that is going to be used to generate a report on the type.
 339            /// </summary>
 340            /// <example>
 341            ///     GDX.Developer.Reports.ObjectInfo,GDX
 342            /// </example>
 343            public readonly string ObjectInfoTypeDefinition;
 344
 345
 346            /// <summary>
 347            ///     A <see cref="string" /> check against a <see cref="UnityEngine.Object" /> name.
 348            /// </summary>
 349            /// <example>
 350            ///     Armor
 351            /// </example>
 352            public readonly string NameFilter;
 353
 354            /// <summary>
 355            ///     Create a <see cref="ResourcesQuery" /> for the given <paramref name="typeDefinition" />, while
 356            ///     attempting to use the provided <paramref name="objectInfoTypeDefinition" /> for report generation.
 357            /// </summary>
 358            /// <remarks>
 359            ///     Uses the default <see cref="ObjectInfo" /> for report generation if
 360            ///     <paramref name="objectInfoTypeDefinition" /> fails to qualify.
 361            /// </remarks>
 362            /// <param name="typeDefinition">The fully qualified type that is going to be evaluated.</param>
 363            /// <param name="objectInfoTypeDefinition">
 364            ///     The fully qualified type that is going to be used to generate a report on the
 365            ///     type. If left null, system will attempt to find an appropriate info generator.
 366            /// </param>
 367            /// <param name="nameFilter">
 368            ///     A string that must be contained in an objects name for it to be valid in the query.
 369            /// </param>
 370            /// <example>
 371            ///     var queryTexture2D = new ResourcesQuery("UnityEngine.Texture2D,UnityEngine",
 372            ///     "GDX.Developer.Reports.ObjectInfos.TextureObjectInfo,GDX", "Armor");
 373            /// </example>
 374            public ResourcesQuery(string typeDefinition, string objectInfoTypeDefinition = null,
 375                string nameFilter = null)
 2376            {
 2377                TypeDefinition = typeDefinition;
 2378                ObjectInfoTypeDefinition = objectInfoTypeDefinition;
 2379                NameFilter = nameFilter;
 2380            }
 381        }
 382    }
 383}
 384#endif