< Summary

Class:DCL.ABConverter.Client
Assembly:ABConverter
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/ABConverter/Client.cs
Covered lines:0
Uncovered lines:179
Coverable lines:179
Total lines:503
Line coverage:0% (0 of 179)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Settings(...)0%2100%
Clone()0%2100%
Client()0%2100%
EnsureEnvironment()0%6200%
BuildAllWearableCollectionsURL()0%20400%
ExportSceneToAssetBundles()0%2100%
ExportSceneToAssetBundles(...)0%2721600%
ConvertScenesToAssetBundles(...)0%30500%
ConvertAssetToAssetBundle(...)0%42600%
DumpArea(...)0%6200%
DumpArea(...)0%6200%
DumpScene(...)0%6200%
DumpAsset(...)0%6200%
DumpAllBodiesWearables()0%6200%
DumpAllNonBodiesWearables()0%6200%
DumpWearableQueue(...)0%6200%
ExtractMappingPairs(...)0%20400%
GetWearableItems(...)0%20400%
GLTFImporter_OnNonBodyWearableLoad(...)0%2100%
GLTFImporter_OnBodyWearableLoad(...)0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/ABConverter/Client.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using DCL.Helpers;
 6using UnityEngine;
 7using UnityEngine.Networking;
 8
 9namespace DCL.ABConverter
 10{
 11    public static class Client
 12    {
 13        private const string COLLECTIONS_FETCH_URL = "https://peer.decentraland.org/lambdas/collections";
 14        private const string WEARABLES_FETCH_URL = "https://peer.decentraland.org/lambdas/collections/wearables?";
 15
 16        public class Settings
 17        {
 18            /// <summary>
 19            /// if set to true, when conversion finishes, the working folder containing all downloaded assets will be de
 20            /// </summary>
 21            public bool deleteDownloadPathAfterFinished = false;
 22
 23            /// <summary>
 24            /// If set to true, Asset Bundles will not be built at all, and only the asset dump will be performed.
 25            /// </summary>
 26            public bool dumpOnly = false;
 27
 28            /// <summary>
 29            /// If set to true, Asset Bundle output folder will be checked, and existing bundles in that folder will be 
 30            /// the conversion process.
 31            /// </summary>
 32            public bool skipAlreadyBuiltBundles = false;
 33
 34            /// <summary>
 35            /// If set to true, the GLTF _Downloads folder and the Asset Bundles folder will be deleted at the beginning
 36            /// </summary>
 037            public bool clearDirectoriesOnStart = true;
 38
 39            /// <summary>
 40            /// Log verbosity.
 41            /// </summary>
 42            public bool verbose = false;
 43
 44            /// <summary>
 45            /// Output folder for asset bundles, by default, they will be stored in Assets/../AssetBundles.
 46            /// </summary>
 047            public string finalAssetBundlePath = Config.ASSET_BUNDLES_PATH_ROOT + Path.DirectorySeparatorChar;
 48
 49            /// <summary>
 50            /// Target top level domain. This will define the content server url used for the conversion (org, zone, etc
 51            /// </summary>
 052            public ContentServerUtils.ApiTLD tld = ContentServerUtils.ApiTLD.ORG;
 53
 54            /// <summary>
 55            /// Raw baseUrl using for asset dumping.
 56            /// </summary>
 57            public string baseUrl;
 58
 059            public Settings Clone() { return this.MemberwiseClone() as Settings; }
 60
 061            public Settings(ContentServerUtils.ApiTLD tld = ContentServerUtils.ApiTLD.ORG)
 62            {
 063                this.tld = tld;
 064                this.baseUrl = ContentServerUtils.GetContentAPIUrlBase(tld);
 065            }
 66        }
 67
 068        private static Logger log = new Logger("ABConverter.Client");
 69        public static Environment env;
 70
 71        public static Environment EnsureEnvironment()
 72        {
 073            if (env == null)
 074                env = Environment.CreateWithDefaultImplementations();
 75
 076            return env;
 77        }
 78
 79        public static string BuildAllWearableCollectionsURL()
 80        {
 081            UnityWebRequest w = UnityWebRequest.Get(COLLECTIONS_FETCH_URL);
 082            w.SendWebRequest();
 83
 084            while (!w.isDone) { }
 85
 086            if (!w.WebRequestSucceded())
 87            {
 088                Debug.LogError($"Request error! Wearable collections at '{COLLECTIONS_FETCH_URL}' couldn't be fetched! -
 089                return null;
 90            }
 91
 092            var collectionsApiData = JsonUtility.FromJson<WearableCollectionsAPIData>(w.downloadHandler.text);
 93
 094            string finalUrl = WEARABLES_FETCH_URL;
 095            finalUrl += "collectionId=" + collectionsApiData.collections[0].id;
 096            for (int i = 1; i < collectionsApiData.collections.Length; i++)
 97            {
 098                finalUrl += "&collectionId=" + collectionsApiData.collections[i].id;
 99            }
 100
 0101            return finalUrl;
 102        }
 103
 104        /// <summary>
 105        /// Batch-mode entry point
 106        /// </summary>
 107        public static void ExportSceneToAssetBundles()
 108        {
 109            //NOTE(Brian): This should make the logs cleaner
 0110            Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);
 0111            Application.SetStackTraceLogType(LogType.Warning, StackTraceLogType.None);
 0112            Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.Full);
 0113            Application.SetStackTraceLogType(LogType.Exception, StackTraceLogType.Full);
 0114            Application.SetStackTraceLogType(LogType.Assert, StackTraceLogType.Full);
 115
 0116            EnsureEnvironment();
 0117            ExportSceneToAssetBundles(System.Environment.GetCommandLineArgs());
 0118        }
 119
 120        /// <summary>
 121        /// Start the conversion process with the given commandLineArgs.
 122        /// </summary>
 123        /// <param name="commandLineArgs">An array with the command line arguments.</param>
 124        /// <exception cref="ArgumentException">When an invalid argument is passed</exception>
 125        public static void ExportSceneToAssetBundles(string[] commandLineArgs)
 126        {
 0127            Settings settings = new Settings();
 128            try
 129            {
 0130                if (Utils.ParseOption(commandLineArgs, Config.CLI_SET_CUSTOM_OUTPUT_ROOT_PATH, 1, out string[] outputPat
 131                {
 0132                    settings.finalAssetBundlePath = outputPath[0] + "/";
 133                }
 134
 0135                if (Utils.ParseOption(commandLineArgs, Config.CLI_SET_CUSTOM_BASE_URL, 1, out string[] customBaseUrl))
 0136                    settings.baseUrl = customBaseUrl[0];
 137
 0138                if (Utils.ParseOption(commandLineArgs, Config.CLI_VERBOSE, 0, out _))
 0139                    settings.verbose = true;
 140
 0141                if (Utils.ParseOption(commandLineArgs, Config.CLI_ALWAYS_BUILD_SYNTAX, 0, out _))
 0142                    settings.skipAlreadyBuiltBundles = false;
 143
 0144                if (Utils.ParseOption(commandLineArgs, Config.CLI_KEEP_BUNDLES_SYNTAX, 0, out _))
 0145                    settings.deleteDownloadPathAfterFinished = false;
 146
 0147                if (Utils.ParseOption(commandLineArgs, Config.CLI_BUILD_SCENE_SYNTAX, 1, out string[] sceneCid))
 148                {
 0149                    if (sceneCid == null || string.IsNullOrEmpty(sceneCid[0]))
 150                    {
 0151                        throw new ArgumentException("Invalid sceneCid argument! Please use -sceneCid <id> to establish t
 152                    }
 153
 0154                    DumpScene(sceneCid[0], settings);
 0155                    return;
 156                }
 157
 0158                if (Utils.ParseOption(commandLineArgs, Config.CLI_BUILD_PARCELS_RANGE_SYNTAX, 4, out string[] xywh))
 159                {
 0160                    if (xywh == null)
 161                    {
 0162                        throw new ArgumentException("Invalid parcelsXYWH argument! Please use -parcelsXYWH x y w h to es
 163                    }
 164
 165                    int x, y, w, h;
 0166                    bool parseSuccess = false;
 167
 0168                    parseSuccess |= int.TryParse(xywh[0], out x);
 0169                    parseSuccess |= int.TryParse(xywh[1], out y);
 0170                    parseSuccess |= int.TryParse(xywh[2], out w);
 0171                    parseSuccess |= int.TryParse(xywh[3], out h);
 172
 0173                    if (!parseSuccess)
 174                    {
 0175                        throw new ArgumentException("Invalid parcelsXYWH argument! Please use -parcelsXYWH x y w h to es
 176                    }
 177
 0178                    if (w > 10 || h > 10 || w < 0 || h < 0)
 179                    {
 0180                        throw new ArgumentException("Invalid parcelsXYWH argument! Please don't use negative width/heigh
 181                    }
 182
 0183                    DumpArea(new Vector2Int(x, y), new Vector2Int(w, h), settings);
 0184                    return;
 185                }
 186
 0187                throw new ArgumentException("Invalid arguments! You must pass -parcelsXYWH or -sceneCid for dump to work
 188            }
 0189            catch (Exception e)
 190            {
 0191                log.Error(e.Message);
 0192            }
 0193        }
 194
 195        /// <summary>
 196        /// This will start the asset bundle conversion for a given scene list, given a scene cids list.
 197        /// </summary>
 198        /// <param name="sceneCidsList">The cid list for the scenes to gather from the catalyst's content server</param>
 199        /// <param name="settings">Any conversion settings object, if its null, a new one will be created</param>
 200        /// <returns>A state context object useful for tracking the conversion progress</returns>
 201        public static Core.State ConvertScenesToAssetBundles(List<string> sceneCidsList, Settings settings = null)
 202        {
 0203            if (sceneCidsList == null || sceneCidsList.Count == 0)
 204            {
 0205                log.Error("Scene list is null or count == 0! Maybe this sector lacks scenes or content requests failed?"
 0206                return new Core.State() { lastErrorCode = Core.ErrorCodes.SCENE_LIST_NULL };
 207            }
 208
 0209            log.Info($"Building {sceneCidsList.Count} scenes...");
 210
 0211            List<ContentServerUtils.MappingPair> rawContents = new List<ContentServerUtils.MappingPair>();
 212
 0213            EnsureEnvironment();
 214
 0215            if (settings == null)
 0216                settings = new Settings();
 217
 0218            foreach (var sceneCid in sceneCidsList)
 219            {
 0220                ContentServerUtils.MappingsAPIData parcelInfoApiData = ABConverter.Utils.GetSceneMappingsData(env.webReq
 0221                rawContents.AddRange(parcelInfoApiData.data[0].content.contents);
 222            }
 223
 0224            var core = new ABConverter.Core(env, settings);
 0225            core.Convert(rawContents.ToArray());
 226
 0227            return core.state;
 228        }
 229
 230        /// <summary>
 231        /// This will start the asset bundle conversion for a single asset
 232        /// </summary>
 233        /// <param name="assetHash">The asset's content server hash</param>
 234        /// <param name="assetFilename">The asset's content server file name</param>
 235        /// <param name="sceneCid">The asset scene ID</param>
 236        /// <param name="settings">Any conversion settings object, if its null, a new one will be created</param>
 237        /// <returns>A state context object useful for tracking the conversion progress</returns>
 238        public static Core.State ConvertAssetToAssetBundle(string assetHash, string assetFilename, string sceneCid, Sett
 239        {
 0240            if (string.IsNullOrEmpty(assetHash))
 241            {
 0242                log.Error("Missing asset hash for ConvertAssetToAssetBundle()");
 0243                return new Core.State() { lastErrorCode = Core.ErrorCodes.UNDEFINED };
 244            }
 245
 0246            if (string.IsNullOrEmpty(assetFilename))
 247            {
 0248                log.Error("Missing asset file name for ConvertAssetToAssetBundle()");
 0249                return new Core.State() { lastErrorCode = Core.ErrorCodes.UNDEFINED };
 250            }
 251
 0252            log.Info($"Building {assetHash} asset...");
 253
 0254            EnsureEnvironment();
 255
 0256            if (settings == null)
 257            {
 0258                settings = new Settings()
 259                {
 260                    skipAlreadyBuiltBundles = false
 261                };
 262            }
 263
 0264            var core = new ABConverter.Core(env, settings);
 265
 0266            List<ContentServerUtils.MappingPair> rawContents = new List<ContentServerUtils.MappingPair>();
 0267            rawContents.Add(new ContentServerUtils.MappingPair
 268            {
 269                file = assetFilename,
 270                hash = assetHash
 271            });
 272
 273            // If the asset is a GLTF we add the dependencies to the rawContents to be downloaded
 0274            if (assetFilename.ToLower().EndsWith(".glb") || assetFilename.ToLower().EndsWith(".gltf"))
 275            {
 0276                core.GetAssetDependenciesMappingPairs(assetHash, assetFilename, sceneCid, ref rawContents);
 277            }
 278
 0279            core.Convert(rawContents.ToArray(), null);
 280
 0281            return core.state;
 282        }
 283
 284        /// <summary>
 285        /// Dump a world area given coords and size. The zone is a rectangle with a center pivot.
 286        /// </summary>
 287        /// <param name="coords">Coords as parcel coordinates</param>
 288        /// <param name="size">Size as radius</param>
 289        /// <param name="settings">Conversion settings</param>
 290        /// <returns>A state context object useful for tracking the conversion progress</returns>
 291        public static Core.State DumpArea(Vector2Int coords, Vector2Int size, Settings settings = null)
 292        {
 0293            EnsureEnvironment();
 294
 0295            if (settings == null)
 0296                settings = new Settings();
 297
 0298            HashSet<string> sceneCids = ABConverter.Utils.GetSceneCids(env.webRequest, settings.tld, coords, size);
 0299            List<string> sceneCidsList = sceneCids.ToList();
 0300            return ConvertScenesToAssetBundles(sceneCidsList, settings);
 301        }
 302
 303        /// <summary>
 304        /// Dump a world area given a parcel coords array.
 305        /// </summary>
 306        /// <param name="coords">A list with the parcels coordinates wanted to be converted</param>
 307        /// <param name="settings">Conversion settings</param>
 308        /// <returns>A state context object useful for tracking the conversion progress</returns>
 309        public static Core.State DumpArea(List<Vector2Int> coords, Settings settings = null)
 310        {
 0311            EnsureEnvironment();
 312
 0313            if (settings == null)
 0314                settings = new Settings();
 315
 0316            HashSet<string> sceneCids = Utils.GetScenesCids(env.webRequest, settings.tld, coords);
 317
 0318            List<string> sceneCidsList = sceneCids.ToList();
 0319            return ConvertScenesToAssetBundles(sceneCidsList, settings);
 320        }
 321
 322        /// <summary>
 323        /// Dump a single world scene given a scene cid.
 324        /// </summary>
 325        /// <param name="cid">The scene cid in the multi-hash format (i.e. Qm...etc)</param>
 326        /// <param name="settings">Conversion settings</param>
 327        /// <returns>A state context object useful for tracking the conversion progress</returns>
 328        public static Core.State DumpScene(string cid, Settings settings = null)
 329        {
 0330            EnsureEnvironment();
 331
 0332            if (settings == null)
 0333                settings = new Settings();
 334
 0335            return ConvertScenesToAssetBundles(new List<string> { cid }, settings);
 336        }
 337
 338        /// <summary>
 339        /// Dump a single asset (and its dependencies) given an asset hash and scene Cid
 340        /// </summary>
 341        /// <param name="assetHash">The asset's content server hash</param>
 342        /// <param name="assetFilename">The asset's content server file name</param>
 343        /// <param name="sceneCid">The asset scene ID</param>
 344        /// <param name="settings">Conversion settings</param>
 345        /// <returns>A state context object useful for tracking the conversion progress</returns>
 346        public static Core.State DumpAsset(string assetHash, string assetFilename, string sceneCid, Settings settings = 
 347        {
 0348            EnsureEnvironment();
 349
 0350            if (settings == null)
 0351                settings = new Settings();
 352
 0353            return ConvertAssetToAssetBundle(assetHash, assetFilename, sceneCid, settings);
 354        }
 355
 356        /// <summary>
 357        /// Dump all bodyshape wearables normally, including their imported skeleton
 358        /// </summary>
 359        public static void DumpAllBodiesWearables()
 360        {
 0361            EnsureEnvironment();
 362
 0363            List<WearableItem> avatarItemList = GetWearableItems(BuildAllWearableCollectionsURL())
 0364                                                .Where(x => x.data.category == WearableLiterals.Categories.BODY_SHAPE)
 365                                                .ToList();
 366
 0367            Queue<WearableItem> itemQueue = new Queue<WearableItem>(avatarItemList);
 0368            var settings = new Settings();
 0369            settings.skipAlreadyBuiltBundles = false;
 0370            settings.deleteDownloadPathAfterFinished = false;
 0371            settings.clearDirectoriesOnStart = false;
 0372            var abConverterCoreController = new ABConverter.Core(ABConverter.Environment.CreateWithDefaultImplementation
 373
 0374            abConverterCoreController.InitializeDirectoryPaths(true);
 0375            DumpWearableQueue(abConverterCoreController, itemQueue, GLTFImporter_OnBodyWearableLoad);
 0376        }
 377
 378        /// <summary>
 379        /// Dump all non-bodyshape wearables, optimized to remove the skeleton for the wearables ABs since that is
 380        /// only needed for the body shapes (and the WearablesController sets it up for non-bodyshapes in runtime)
 381        /// </summary>
 382        public static void DumpAllNonBodiesWearables()
 383        {
 0384            EnsureEnvironment();
 385
 386            // For debugging purposes we can intercept this item list with LinQ for specific wearables
 0387            List<WearableItem> avatarItemList = GetWearableItems(BuildAllWearableCollectionsURL())
 0388                                                .Where(x => x.data.category != WearableLiterals.Categories.BODY_SHAPE)
 389                                                .ToList();
 390
 0391            Queue<WearableItem> itemQueue = new Queue<WearableItem>(avatarItemList);
 0392            var settings = new Settings();
 0393            settings.skipAlreadyBuiltBundles = false;
 0394            settings.deleteDownloadPathAfterFinished = false;
 0395            settings.clearDirectoriesOnStart = false;
 0396            var abConverterCoreController = new ABConverter.Core(ABConverter.Environment.CreateWithDefaultImplementation
 397
 0398            abConverterCoreController.InitializeDirectoryPaths(true);
 0399            DumpWearableQueue(abConverterCoreController, itemQueue, GLTFImporter_OnNonBodyWearableLoad);
 0400        }
 401
 402        /// <summary>
 403        /// Given a list of WearableItems, each one is downloaded along with its dependencies and converted to ABs recur
 404        /// (to avoid mixing same-name dependencies between wearables)
 405        /// </summary>
 406        /// <param name="abConverterCoreController">an instance of the ABCore</param>
 407        /// <param name="items">an already-populated list of WearableItems</param>
 408        /// <param name="OnWearableLoad">an action to be bind to the OnWearableLoad event on each wearable</param>
 409        private static void DumpWearableQueue(ABConverter.Core abConverterCoreController, Queue<WearableItem> items, Sys
 410        {
 411            // We toggle the core's ABs generation off so that we execute that conversion here when there is no more ite
 0412            abConverterCoreController.generateAssetBundles = false;
 413
 0414            if (items.Count == 0)
 415            {
 0416                abConverterCoreController.ConvertDumpedAssets();
 417
 0418                return;
 419            }
 420
 0421            Debug.Log("Building wearables... items left... " + items.Count);
 422
 0423            var pairs = ExtractMappingPairs(new List<WearableItem>() { items.Dequeue() });
 424
 0425            UnityGLTF.GLTFImporter.OnGLTFWillLoad += OnWearableLoad;
 426
 0427            abConverterCoreController.Convert(pairs.ToArray(),
 428                (err) =>
 429                {
 0430                    UnityGLTF.GLTFImporter.OnGLTFWillLoad -= OnWearableLoad;
 0431                    abConverterCoreController.CleanupWorkingFolders();
 0432                    DumpWearableQueue(abConverterCoreController, items, OnWearableLoad);
 0433                });
 0434        }
 435
 436        /// <summary>
 437        /// Given a list of WearableItems, extracts and returns a list of MappingPairs
 438        /// </summary>
 439        /// <param name="wearableItems">A list of already-populated WearableItems</param>
 440        /// <returns>A list of the extracted Wearables MappingPairs</returns>
 441        private static List<ContentServerUtils.MappingPair> ExtractMappingPairs(List<WearableItem> wearableItems)
 442        {
 0443            var result = new List<ContentServerUtils.MappingPair>();
 444
 0445            foreach (var wearable in wearableItems)
 446            {
 0447                foreach (var representation in wearable.data.representations)
 448                {
 0449                    foreach (var datum in representation.contents)
 450                    {
 0451                        result.Add(new ContentServerUtils.MappingPair() { file = datum.key, hash = datum.hash });
 452                    }
 453                }
 454            }
 455
 0456            return result;
 457        }
 458
 459        /// <summary>
 460        /// Given a base-url to fetch wearables collections, returns a list of all the WearableItems
 461        /// </summary>
 462        /// <param name="url">base-url to fetch the wearables collections</param>
 463        /// <returns>A list of all the WearableItems found</returns>
 464        private static List<WearableItem> GetWearableItems(string url)
 465        {
 0466            UnityWebRequest w = UnityWebRequest.Get(url);
 0467            w.SendWebRequest();
 468
 0469            while (!w.isDone) { }
 470
 0471            if (!w.WebRequestSucceded())
 472            {
 0473                Debug.LogError($"Request error! Wearable at '{url}' couldn't be fetched! -- {w.error}");
 0474                return null;
 475            }
 476
 0477            var wearablesApiData = JsonUtility.FromJson<WearablesAPIData>(w.downloadHandler.text);
 0478            var resultList = wearablesApiData.GetWearableItems();
 479
 480            // Since the wearables deployments response returns only a batch of elements, we need to fetch all the
 481            // batches sequentially
 0482            if (!string.IsNullOrEmpty(wearablesApiData.pagination.next))
 483            {
 0484                var nextPageResults = GetWearableItems(WEARABLES_FETCH_URL + wearablesApiData.pagination.next);
 0485                resultList.AddRange(nextPageResults);
 486            }
 487
 0488            return resultList;
 489        }
 490
 491        private static void GLTFImporter_OnNonBodyWearableLoad(UnityGLTF.GLTFSceneImporter obj)
 492        {
 0493            obj.importSkeleton = false;
 0494            obj.maxTextureSize = 512;
 0495        }
 496
 497        private static void GLTFImporter_OnBodyWearableLoad(UnityGLTF.GLTFSceneImporter obj)
 498        {
 0499            obj.importSkeleton = true;
 0500            obj.maxTextureSize = 512;
 0501        }
 502    }
 503}