< Summary

Class:GDX.Platform
Assembly:GDX
File(s):./Packages/com.dotbunny.gdx/GDX/Platform.cs
Covered lines:54
Uncovered lines:52
Coverable lines:106
Total lines:285
Line coverage:50.9% (54 of 106)
Covered branches:0
Total branches:0
Covered methods:6
Total methods:11
Method coverage:54.5% (6 of 11)

Coverage History

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
EnsureFolderHierarchyExists(...)0%4.123050%
EnsureFileFolderHierarchyExists(...)0%2100%
EnsureFileWritable(...)0%20400%
ForceDeleteFile(...)0%220100%
GetHardwareGeneration()0%2100%
GetOutputFolder(...)0%6.646073.91%
GetRandomSafeCharacter(...)0%110100%
GetUniqueOutputFilePath(...)0%3.493062.07%
IsFocused()0%2100%
IsFileWritable(...)0%20400%
IsHeadless()0%110100%

File(s)

./Packages/com.dotbunny.gdx/GDX/Platform.cs

#LineLine coverage
 1// Copyright (c) 2020-2024 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
 5using System;
 6using System.IO;
 7using System.Runtime.CompilerServices;
 8using System.Text;
 9using GDX.Developer;
 10using GDX.Mathematics.Random;
 11using UnityEngine;
 12using UnityEngine.Rendering;
 13
 14namespace GDX
 15{
 16    /// <summary>
 17    ///     A collection of platform related helper utilities.
 18    /// </summary>
 19    [VisualScriptingCompatible(8)]
 20    public static class Platform
 21    {
 22        public const float ImageCompareTolerance = 0.99f;
 23        public const float FloatTolerance = 0.000001f;
 24        public const double DoubleTolerance = 0.000001d;
 25        public const string SafeCharacterPool = "abcdefghijklmnopqrstuvwxyz";
 26        public const int CharacterPoolLength = 25;
 27        public const int CharacterPoolLengthExclusive = 24;
 28
 29        /// <summary>
 30        ///     A filename safe version of the timestamp format.
 31        /// </summary>s
 32        public const string FilenameTimestampFormat = "yyyyMMdd_HHmmss";
 33        public const string TimestampFormat = "yyyy-MM-dd HH:mm:ss";
 34
 35        static string s_OutputFolder;
 36
 37        /// <summary>
 38        ///     Validate that all directories are created for a given <paramref name="folderPath" />.
 39        /// </summary>
 40        /// <param name="folderPath">The path to process and validate.</param>
 41        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 42        public static void EnsureFolderHierarchyExists(string folderPath)
 643        {
 644            if (!string.IsNullOrEmpty(folderPath) && !Directory.Exists(folderPath))
 045            {
 046                Directory.CreateDirectory(folderPath);
 047            }
 648        }
 49
 50        /// <summary>
 51        ///     Validate that all parent directories are created for a given <paramref name="filePath" />.
 52        /// </summary>
 53        /// <param name="filePath">The path to process and validate.</param>
 54        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 55        public static void EnsureFileFolderHierarchyExists(string filePath)
 056        {
 057            string targetDirectory = Path.GetDirectoryName(filePath);
 058            EnsureFolderHierarchyExists(targetDirectory);
 059        }
 60
 61        /// <summary>
 62        ///     Validate that the file path is writable, making the necessary folder structure and setting permissions.
 63        /// </summary>
 64        /// <param name="filePath">The absolute path to validate.</param>
 65        public static void EnsureFileWritable(string filePath)
 066        {
 067            string fileName = Path.GetFileName(filePath);
 068            if (fileName != null)
 069            {
 070                string directoryPath = filePath.TrimEnd(fileName.ToCharArray());
 071                if (!Directory.Exists(directoryPath))
 072                {
 073                    Directory.CreateDirectory(directoryPath);
 074                }
 075            }
 76
 077            if (File.Exists(filePath))
 078            {
 079                File.SetAttributes(filePath, File.GetAttributes(filePath) & ~FileAttributes.ReadOnly);
 080            }
 081        }
 82
 83        /// <summary>
 84        ///     Use our best attempt to remove a file at the designated <paramref name="filePath" />.
 85        /// </summary>
 86        /// <param name="filePath">The file path to remove forcefully.</param>
 87        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88        public static void ForceDeleteFile(string filePath)
 889        {
 890            if (File.Exists(filePath))
 291            {
 292                File.SetAttributes(filePath, File.GetAttributes(filePath) & ~FileAttributes.ReadOnly);
 293                File.Delete(filePath);
 294            }
 895        }
 96
 97        /// <summary>
 98        ///     Gets the current platforms hardware generation number?
 99        /// </summary>
 100        /// <returns>Returns 0 for base hardware, 1 for updates.</returns>
 101        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 102        public static int GetHardwareGeneration()
 0103        {
 104#if UNITY_XBOXONE && !UNITY_EDITOR
 105            if (Hardware.version == HardwareVersion.XboxOneX_Devkit ||
 106                Hardware.version == HardwareVersion.XboxOneX_Retail)
 107            {
 108                return 1;
 109            }
 110            return 0;
 111#elif UNITY_PS4 && !UNITY_EDITOR
 112            return 1;
 113#else
 0114            return 0;
 115#endif // UNITY_XBOXONE && !UNITY_EDITOR
 0116        }
 117
 118        /// <summary>
 119        ///     Returns a runtime writable folder.
 120        /// </summary>
 121        /// <param name="folderName">An optional additional folder under the provided path that will be created if neces
 122        /// <returns>The full path to a writable folder at runtime.</returns>
 123        /// <remarks>
 124        ///     Depending on the platform, different routes are taken to finding a writable folder.
 125        ///     <list type="table">
 126        ///         <item>
 127        ///             <term>Editor</term>
 128        ///             <description>The project's root folder is used in this case.</description>
 129        ///         </item>
 130        ///         <item>
 131        ///             <term>Standard Player</term>
 132        ///             <description>Utilizes <see cref="Application.persistentDataPath" /> to find a suitable place.</d
 133        ///         </item>
 134        ///         <item>
 135        ///             <term>DOTS Runtime</term>
 136        ///             <description>Uses <see cref="Directory.GetCurrentDirectory()" />.</description>
 137        ///         </item>
 138        ///     </list>
 139        ///     The path can be overridden by assigning GDX_OUTPUT_FOLDER in the launching arguments.
 140        /// </remarks>
 141        public static string GetOutputFolder(string folderName = null)
 20142        {
 20143            if (s_OutputFolder != null && string.IsNullOrEmpty(folderName))
 14144            {
 14145                return s_OutputFolder;
 146            }
 147
 6148            if (s_OutputFolder == null)
 2149            {
 2150                if (CommandLineParser.Arguments.ContainsKey("GDX_OUTPUT_FOLDER"))
 0151                {
 152                    // Assign and remove quotes
 0153                    s_OutputFolder = CommandLineParser.Arguments["GDX_OUTPUT_FOLDER"]
 154                        .Replace("\"", "");
 0155                }
 156                else
 2157                {
 2158                    s_OutputFolder =
 159#if UNITY_EDITOR
 160                        Path.Combine(Application.dataPath, "..");
 161#elif UNITY_DOTSRUNTIME
 162                        Directory.GetCurrentDirectory();
 163#else
 164                        Application.persistentDataPath;
 165#endif // UNITY_EDITOR
 2166                }
 167
 168                // Cleanup the folder pathing
 2169                s_OutputFolder = Path.GetFullPath(s_OutputFolder);
 170
 171                // Ensure that it is created
 2172                EnsureFolderHierarchyExists(s_OutputFolder);
 2173            }
 174
 6175            if (string.IsNullOrEmpty(folderName))
 2176            {
 2177                return s_OutputFolder;
 178            }
 179
 4180            string fullPath = Path.Combine(s_OutputFolder, folderName);
 4181            EnsureFolderHierarchyExists(fullPath);
 4182            return fullPath;
 20183        }
 184
 185        public static char GetRandomSafeCharacter(IRandomProvider random)
 24186        {
 24187            return SafeCharacterPool[random.NextInteger(0, CharacterPoolLengthExclusive)];
 24188        }
 189
 190        public static string GetUniqueOutputFilePath(string prefix = "GDX_", string extension = ".log",
 191            string folderName = null)
 4192        {
 4193            string tempFolder = GetOutputFolder(folderName);
 4194            StringBuilder tmpFileName = new StringBuilder(260);
 4195            tmpFileName.Append(prefix);
 4196            RandomWrapper random = new RandomWrapper(
 197                DateTime.Now.Ticks.ToString().GetStableHashCode());
 198
 4199            tmpFileName.Append(GetRandomSafeCharacter(random));
 4200            tmpFileName.Append(GetRandomSafeCharacter(random));
 4201            tmpFileName.Append(GetRandomSafeCharacter(random));
 4202            tmpFileName.Append(GetRandomSafeCharacter(random));
 4203            tmpFileName.Append(GetRandomSafeCharacter(random));
 204
 4205            while (true)
 4206            {
 4207                tmpFileName.Append(GetRandomSafeCharacter(random));
 4208                string filePath = Path.Combine(tempFolder, $"{tmpFileName}{extension}");
 4209                if (!File.Exists(filePath))
 4210                {
 4211                    return filePath;
 212                }
 213
 0214                if (tmpFileName.Length <= 260)
 0215                {
 0216                    continue;
 217                }
 218
 0219                tmpFileName.Clear();
 0220                tmpFileName.Append(prefix);
 0221                tmpFileName.Append(GetRandomSafeCharacter(random));
 0222                tmpFileName.Append(GetRandomSafeCharacter(random));
 0223                tmpFileName.Append(GetRandomSafeCharacter(random));
 0224                tmpFileName.Append(GetRandomSafeCharacter(random));
 0225                tmpFileName.Append(GetRandomSafeCharacter(random));
 0226            }
 4227        }
 228
 229#if !UNITY_DOTSRUNTIME
 230        /// <summary>
 231        ///     Is the application focused?
 232        /// </summary>
 233        /// <remarks>
 234        ///     There are issues on some platforms with getting an accurate reading.
 235        /// </remarks>
 236        /// <returns>true/false if the application has focus.</returns>
 237        /// <exception cref="UnsupportedRuntimeException">Not supported on DOTS Runtime.</exception>
 238        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 239        public static bool IsFocused()
 0240        {
 241#if UNITY_XBOXONE && !UNITY_EDITOR
 242            return !XboxOnePLM.AmConstrained();
 243#elif UNITY_PS4 && !UNITY_EDITOR
 244            return true;
 245#else
 0246            return Application.isFocused;
 247#endif // UNITY_XBOXONE && !UNITY_EDITOR
 0248        }
 249#endif // !UNITY_DOTSRUNTIME
 250
 251        /// <summary>
 252        ///     Is it safe to write to the indicated <paramref name="filePath" />?
 253        /// </summary>
 254        /// <param name="filePath">The file path to check if it can be written.</param>
 255        /// <returns>true/false if the path can be written too.</returns>
 256        public static bool IsFileWritable(string filePath)
 0257        {
 0258            if (File.Exists(filePath))
 0259            {
 0260                FileAttributes attributes = File.GetAttributes(filePath);
 0261                if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly ||
 262                    (attributes & FileAttributes.Offline) == FileAttributes.Offline)
 0263                {
 0264                    return false;
 265                }
 0266            }
 267
 0268            return true;
 0269        }
 270
 271#if !UNITY_DOTSRUNTIME
 272        /// <summary>
 273        ///     Is the application running in headless mode?.
 274        /// </summary>
 275        /// <remarks>Useful for detecting running a server.</remarks>
 276        /// <returns>true/false if the application is without an initialized graphics device.</returns>
 277        /// <exception cref="UnsupportedRuntimeException">Not supported on DOTS Runtime.</exception>
 278        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 279        public static bool IsHeadless()
 2280        {
 2281            return SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null;
 2282        }
 283#endif // !UNITY_DOTSRUNTIME
 284    }
 285}

Coverage by test methods












Methods/Properties

EnsureFolderHierarchyExists(System.String)
EnsureFileFolderHierarchyExists(System.String)
EnsureFileWritable(System.String)
ForceDeleteFile(System.String)
GetHardwareGeneration()
GetOutputFolder(System.String)
GetRandomSafeCharacter(GDX.Mathematics.Random.IRandomProvider)
GetUniqueOutputFilePath(System.String, System.String, System.String)
IsFocused()
IsFileWritable(System.String)
IsHeadless()