using System; using System.Collections.Generic; using System.IO; using UnityEditor.AddressableAssets.Settings; using UnityEditor.Build.Content; using UnityEngine; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement.ResourceProviders; namespace UnityEditor.AddressableAssets.Build.Layout { /// /// A storage class used to gather data about an Addressable build. /// [Serializable] public class BuildLayout { /// /// Helper class to wrap header values for BuildLayout /// public class LayoutHeader { /// /// Build layout for this header /// internal BuildLayout m_BuildLayout; /// /// Build Platform Addressables build is targeting /// public BuildTarget BuildTarget { get { if (m_BuildLayout == null) return BuildTarget.NoTarget; return m_BuildLayout.BuildTarget; } } /// /// Hash of the build results /// public string BuildResultHash { get { if (m_BuildLayout == null) return null; return m_BuildLayout.BuildResultHash; } } /// /// If the build was a new build or an update for a previous build /// public BuildType BuildType { get { if (m_BuildLayout == null) return BuildType.NewBuild; return m_BuildLayout.BuildType; } } /// /// DateTime at the start of building Addressables /// public DateTime BuildStart { get { if (m_BuildLayout == null) return DateTime.MinValue; return m_BuildLayout.BuildStart; } } /// /// Time in seconds taken to build Addressables Content /// public double Duration { get { if (m_BuildLayout == null) return 0; return m_BuildLayout.Duration; } } /// /// Null or Empty if the build completed successfully, else contains error causing the failure /// public string BuildError { get { if (m_BuildLayout == null) return ""; return m_BuildLayout.BuildError; } } } /// /// Helper object to get header values for this build layout /// public LayoutHeader Header { get { if (m_Header == null) m_Header = new LayoutHeader() {m_BuildLayout = this}; return m_Header; } } private LayoutHeader m_Header; #region HeaderValues // Any values in here should also be in BuildLayoutHeader class /// /// Build Platform Addressables build is targeting /// public BuildTarget BuildTarget; /// /// Hash of the build results /// public string BuildResultHash; /// /// If the build was a new build or an update for a previous build /// public BuildType BuildType; /// /// DateTime at the start of building Addressables /// public DateTime BuildStart { get { if (m_BuildStartDateTime.Year > 2000) return m_BuildStartDateTime; if (DateTime.TryParse(BuildStartTime, out DateTime result)) { m_BuildStartDateTime = result; return m_BuildStartDateTime; } return DateTime.MinValue; } set { BuildStartTime = value.ToString(); } } private DateTime m_BuildStartDateTime; [SerializeField] internal string BuildStartTime; /// /// Time in seconds taken to build Addressables Content /// public double Duration; /// /// Null or Empty if the build completed successfully, else contains error causing the failure /// public string BuildError; #endregion // End of header values /// /// Version of the Unity edtior used to perform the build. /// public string UnityVersion; /// /// Version of the Addressables package used to perform the build. /// public string PackageVersion; /// /// Player build version for the build, this is a timestamp if PlayerVersionOverride is not set in the settings /// public string PlayerBuildVersion; /// /// Settings used by the Addressables settings at the time of building /// public AddressablesEditorData AddressablesEditorSettings; /// /// Values used by the Addressables runtime /// public AddressablesRuntimeData AddressablesRuntimeSettings; /// /// Name of the build script to build /// public string BuildScript; /// /// Default group at the time of building /// [SerializeReference] public Group DefaultGroup; /// /// The Addressable Groups that reference this data /// [SerializeReference] public List Groups = new List(); /// /// The List of AssetBundles that were built without a group associated to them, such as the BuiltIn Shaders Bundle and the MonoScript Bundle /// [SerializeReference] public List BuiltInBundles = new List(); /// /// List of assets with implicitly included Objects /// public List DuplicatedAssets = new List(); /// /// The build path on disk of the default local content catalog /// [SerializeField] internal string LocalCatalogBuildPath; /// /// The build path of the remote content catalog, if one was built /// [SerializeField] internal string RemoteCatalogBuildPath; internal string m_FilePath; private bool m_HeaderRead = false; private bool m_BodyRead = false; private FileStream m_FileStream = null; private StreamReader m_StreamReader = null; /// /// Used for serialising the header info for the BuildLayout. /// Names must match values in BuildLayout class /// [Serializable] private class BuildLayoutHeader { public BuildTarget BuildTarget; public string BuildResultHash; public BuildType BuildType; public string BuildStartTime; public double Duration; public string BuildError; } /// /// /// /// Path to the BuildLayout json file on disk /// If the basic header information should be read /// If the full build layout should be read /// public static BuildLayout Open(string path, bool readHeader = true, bool readFullFile = false) { if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) { Debug.LogError($"Invalid path provided : {path}"); return null; } BuildLayout readLayout = new BuildLayout { m_FilePath = path }; if (readFullFile) readLayout.ReadFull(); else if (readHeader) readLayout.ReadHeader(); return readLayout; } /// /// Writes json file for the build layout to the destination path /// /// File path to write build layout /// If json should be written using pretty print public void WriteToFile(string destinationPath, bool prettyPrint) { Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); string versionElementString = "\"UnityVersion\":"; string headerJson = null; string bodyJson = JsonUtility.ToJson(this, prettyPrint); if (prettyPrint) { BuildLayoutHeader header = new BuildLayoutHeader() { BuildTarget = this.BuildTarget, BuildResultHash = this.BuildResultHash, BuildType = this.BuildType, BuildStartTime = this.BuildStartTime, Duration = this.Duration, BuildError = this.BuildError }; headerJson = JsonUtility.ToJson(header, false); headerJson = headerJson.Remove(headerJson.Length - 1, 1) + ','; } int index = bodyJson.IndexOf(versionElementString); if (prettyPrint) bodyJson = bodyJson.Remove(0, index); else bodyJson = bodyJson.Insert(index, "\n"); using (FileStream s = System.IO.File.Open(destinationPath, FileMode.Create)) { using (StreamWriter sw = new StreamWriter(s)) { if (prettyPrint) sw.WriteLine(headerJson); sw.Write(bodyJson); } } } /// /// Closes streams for loading the build layout /// public void Close() { if (m_StreamReader != null) { m_StreamReader.Close(); m_StreamReader = null; } if (m_FileStream != null) { m_FileStream.Close(); m_FileStream = null; } } /// /// Reads basic information about the build layout /// /// If false, the file will be closed after reading the header line. /// true is successful, else false public bool ReadHeader(bool keepFileStreamsActive = false) { if (m_HeaderRead) return true; if (string.IsNullOrEmpty(m_FilePath)) { Debug.LogError("Cannot read BuildLayout header, A file has not been selected to open. Open must be called before reading any data"); return false; } try { if (m_FileStream == null) { m_FileStream = System.IO.File.Open(m_FilePath, FileMode.Open); m_StreamReader = new StreamReader(m_FileStream); } string fileJsonText = m_StreamReader.ReadLine(); int lastComma = fileJsonText.LastIndexOf(','); if (lastComma > 0) { fileJsonText = fileJsonText.Remove(lastComma) + '}'; try { EditorJsonUtility.FromJsonOverwrite(fileJsonText, this); } catch (Exception e) { Debug.LogError($"Failed to read header for BuildLayout at {m_FilePath}, with exception: {e.Message}"); return false; } } else { Debug.LogError($"Failed to read header for BuildLayout at {m_FilePath}, invalid json format"); return false; } m_HeaderRead = true; } catch (Exception e) { Debug.LogException(e); return false; } finally { if (!keepFileStreamsActive) Close(); } return true; } /// /// Reads the full build layout data from file /// /// true is successful, else false public bool ReadFull() { if (m_BodyRead) return true; if (string.IsNullOrEmpty(m_FilePath)) { Debug.LogError("Cannot read BuildLayout header, BuildLayout has not open for a file"); return false; } try { if (m_FileStream == null) { m_FileStream = System.IO.File.Open(m_FilePath, FileMode.Open); m_StreamReader = new StreamReader(m_FileStream); } else if (m_HeaderRead) { // reset to read the whole file m_FileStream.Position = 0; m_StreamReader.DiscardBufferedData(); } string fileJsonText = m_StreamReader.ReadToEnd(); EditorJsonUtility.FromJsonOverwrite(fileJsonText, this); m_HeaderRead = true; m_BodyRead = true; } catch (Exception e) { Debug.LogError($"Failed to read header for BuildLayout at {m_FilePath}, with exception: {e.Message}"); return false; } finally { Close(); } return true; } /// /// Values set for the AddressablesAssetSettings at the time of building /// [Serializable] public class AddressablesEditorData { /// /// Hash value of the settings at the time of building /// public string SettingsHash; /// /// Active Addressables profile set at time of Building /// public Profile ActiveProfile; /// /// Addressables setting value set for building the remote catalog /// public bool BuildRemoteCatalog; /// /// Load path for the remote catalog if enabled /// public string RemoteCatalogLoadPath; /// /// Addressables setting value set for bundling the local catalog /// public bool BundleLocalCatalog; /// /// Addressables setting value set for optimising the catalog size /// public bool OptimizeCatalogSize; /// /// Addressables setting value set for time out when downloading catalogs /// public int CatalogRequestsTimeout; /// /// Runtime setting value set for the maximum number of concurrent web requests /// public int MaxConcurrentWebRequests; /// /// Addressables setting value set for is to update the remote catalog on startup /// public bool DisableCatalogUpdateOnStartup; /// /// Addressables setting value set for if the build created unique bundle ids /// public bool UniqueBundleIds; /// /// Addressables setting value set for if the build used non recursive dependency calculation /// public bool NonRecursiveBuilding; /// /// Addressables setting value set for if the build used contiguous bundle objects /// public bool ContiguousBundles; /// /// Addressables setting value set for disabling sub asset representation in the Bundle /// public bool DisableSubAssetRepresentations; /// /// Internal naming prefix of the built in shaders bundle /// public string ShaderBundleNaming; /// /// Internal naming prefix of the monoScript bundle, /// No MonoScript bundle is built if set to disabled /// public string MonoScriptBundleNaming; /// /// Addressables setting value set for is the unity version was stripped from the built bundles /// public bool StripUnityVersionFromBundleBuild; } /// /// Values set for runtime initialisation of Addressables /// [Serializable] public class AddressablesRuntimeData { /// /// Runtime setting value set for if the runtime will submit profiler events /// public bool ProfilerEvents; /// /// Runtime setting value set for if resource manager exceptions are logged or not /// public bool LogResourceManagerExceptions; /// /// Runtime setting value set for catalogs to load (First catalog found in the list is used) /// public List CatalogLoadPaths = new List(); /// /// Hash of the build catalog /// public string CatalogHash; } /// /// Information about the AssetBundleObject /// [Serializable] public class AssetBundleObjectInfo { /// /// The size, in bytes, of the AssetBundleObject /// public ulong Size; } /// /// Key value pair of string type /// [Serializable] public struct StringPair { /// /// String key /// public string Key; /// /// String value /// public string Value; } /// /// Addressables Profile data /// [Serializable] public class Profile { /// /// Name of the profile /// public string Name; /// /// ID assigned within the ProfileSettings of the profile /// public string Id; /// /// Profile variables assigned to the profile /// public StringPair[] Values; } /// /// Data about the AddressableAssetGroup that gets processed during a build. /// [Serializable] public class Group { /// /// The Name of the AdressableAssetGroup /// public string Name; /// /// The Guid of the AddressableAssetGroup /// public string Guid; /// /// The packing mode as defined by the BundledAssetGroupSchema on the AddressableAssetGroup /// public string PackingMode; /// /// A list of the AssetBundles associated with the Group /// [SerializeReference] public List Bundles = new List(); /// /// Data about the AddressableAssetGroupSchemas associated with the Group /// [SerializeReference] public List Schemas = new List(); } /// /// Data container for AddressableAssetGroupSchemas /// [Serializable] public class SchemaData : ISerializationCallbackReceiver { /// /// The Guid of the AddressableAssetGroupSchema /// public string Guid; /// /// The class type of the AddressableAssetGroupSchema /// public string Type; /// /// These key-value-pairs include data about the AddressableAssetGroupSchema, such as PackingMode and Compression. /// public List> KvpDetails = new List>(); [SerializeField] private StringPair[] SchemaDataPairs; /// /// Converts the unserializable KvpDetails to a serializable type for writing /// public void OnBeforeSerialize() { SchemaDataPairs = new StringPair[KvpDetails.Count]; for (int i = 0; i < SchemaDataPairs.Length; ++i) SchemaDataPairs[i] = new StringPair() {Key = KvpDetails[i].Item1, Value = KvpDetails[i].Item2}; } /// /// Writes data to KvpDetails after Deserializing to temporary data fields /// public void OnAfterDeserialize() { for (int i = 0; i < SchemaDataPairs.Length; ++i) KvpDetails.Add(new Tuple(SchemaDataPairs[i].Key, SchemaDataPairs[i].Value)); SchemaDataPairs = null; } } /// /// Data store for AssetBundle information. /// [Serializable] public class Bundle { /// /// The name of the AssetBundle /// public string Name; /// /// Name used to identify the asset bundle /// public string InternalName; /// /// The file size of the AssetBundle on disk, in bytes /// public ulong FileSize; /// /// Status of the bundle after an update build /// public BundleBuildStatus BuildStatus; /// /// The file size of all of the Expanded Dependencies of this AssetBundle, in bytes /// Expanded dependencies are the dependencies of this AssetBundle's dependencies /// public ulong ExpandedDependencyFileSize; /// /// The file size /// public ulong DependencyFileSize; /// /// The file size of the AssetBundle on disk when uncompressed, in bytes /// public ulong UncompressedFileSize { get { ulong total = 0; foreach (File file in Files) total += file.UncompressedSize; return total; } } /// /// The number of Assets contained within the bundle /// public int AssetCount = 0; /// /// Represents a dependency from the containing Bundle to dependentBundle, with AssetDependencies representing each of the assets in parentBundle that create the link to dependentBundle /// [Serializable] public class BundleDependency { /// /// The bundle that the parent bundle depends on /// [SerializeReference] public Bundle DependencyBundle; /// /// The list of assets that link the parent bundle to the DependencyBundle /// public List AssetDependencies; /// /// Percentage of Efficiency asset usage that uses the entire dependency tree of this bundle dependency. /// This includes DependencyBundle and all bundles beneath it. /// Value is equal to [Total Filesize of Dependency Assets] / [Total size of all dependency bundles on disk] /// Example: There are 3 bundles A, B, and C, that are each 10 MB on disk. A depends on 2 MB worth of assets in B, and B depends on 4 MB worth of assets in C. /// The Efficiency of the dependencyLink from A->B would be 2/10 -> 20% and the ExpandedEfficiency of A->B would be (2 + 4)/(10 + 10) -> 6/20 -> 30% /// public float ExpandedEfficiency; /// /// The Efficiency of the connection between the parent bundle and DependencyBundle irrespective of the full dependency tree below DependencyBundle. /// Value is equal to [Serialized Filesize of assets In Dependency Bundle Referenced By Parent]/[Total size of Dependency Bundle on disk] /// Example: Given two Bundles A and B that are each 10 MB on disk, and A depends on 5 MB worth of assets in B, then the Efficiency of DependencyLink A->B is 5/10 = .5 /// public float Efficiency; private HashSet referencedAssets = new HashSet(); /// /// The number of uniquely assets that the parent bundle uniquely references in dependency bundle. This is used to calculate Efficiency without double counting. /// internal ulong referencedAssetsFileSize = 0; internal BundleDependency(Bundle b) { DependencyBundle = b; AssetDependencies = new List(); } internal void CreateAssetDependency(ExplicitAsset root, ExplicitAsset dependencyAsset) { if (referencedAssets.Contains(dependencyAsset)) return; referencedAssets.Add(dependencyAsset); AssetDependencies.Add(new AssetDependency(root, dependencyAsset)); referencedAssetsFileSize += dependencyAsset.SerializedSize; } /// /// Represents a dependency from a root Asset to a dependent Asset. /// [Serializable] public struct AssetDependency { [SerializeReference] internal ExplicitAsset rootAsset; [SerializeReference] internal ExplicitAsset dependencyAsset; internal AssetDependency(ExplicitAsset root, ExplicitAsset depAsset) { rootAsset = root; dependencyAsset = depAsset; } } } internal Dictionary BundleDependencyMap = new Dictionary(); /// /// A list of bundles that this bundle depends upon. /// [SerializeField] public BundleDependency[] BundleDependencies = Array.Empty(); /// /// Convert BundleDependencyMap to a format that is able to be serialized and plays nicer with /// CalculateEfficiency - this must be called on a bundle before CalculateEfficiency can be called. /// internal void SerializeBundleToBundleDependency() { BundleDependencies = new BundleDependency[BundleDependencyMap.Values.Count]; BundleDependencyMap.Values.CopyTo(BundleDependencies, 0); } /// /// Updates the BundleDependency from the current bundle to the bundle that contains referencedAsset. If no such BundleDependency exists, /// one is created. Does nothing if rootAsset's bundle is not the current bundle or /// if the two assets are in the same bundle. /// /// /// internal void UpdateBundleDependency(ExplicitAsset rootAsset, ExplicitAsset referencedAsset) { if (rootAsset.Bundle != this || referencedAsset.Bundle == rootAsset.Bundle) return; if (!BundleDependencyMap.ContainsKey(referencedAsset.Bundle)) BundleDependencyMap.Add(referencedAsset.Bundle, new BundleDependency(referencedAsset.Bundle)); BundleDependencyMap[referencedAsset.Bundle].CreateAssetDependency(rootAsset, referencedAsset); } // Helper struct for calculating Efficiency internal struct EfficiencyInfo { internal ulong totalAssetFileSize; internal ulong referencedAssetFileSize; } /// /// The Compression method used for the AssetBundle. /// public string Compression; /// /// Cyclic redundancy check of the content contained inside of the asset bundle. /// This value will not change between identical asset bundles with different compression options. /// public uint CRC; /// /// The hash version of the contents contained inside of the asset bundle. /// This value will not change between identical asset bundles with different compression options. /// public Hash128 Hash; /// /// A reference to the Group data that this AssetBundle was generated from /// [SerializeReference] public Group Group; /// /// Path Provider uses to load the Asset Bundle /// public string LoadPath; /// /// Provider used to load the Asset Bundle /// public string Provider; /// /// Result provided by the Provider loading the Asset Bundle /// public string ResultType; /// /// List of the Files referenced by the AssetBundle /// [SerializeReference] public List Files = new List(); /// /// A list of the bundles that directly depend on this AssetBundle /// [SerializeReference] public List DependentBundles = new List(); /// /// A list of the direct dependencies of the AssetBundle /// [SerializeReference] public List Dependencies; /// /// The second order dependencies and greater of a bundle /// [SerializeReference] public List ExpandedDependencies; } /// /// Data store for resource files generated by the build pipeline and referenced by a main File /// [Serializable] public class SubFile { /// /// The name of the sub-file /// public string Name; /// /// If the main File is a serialized file, this will be true. /// public bool IsSerializedFile; /// /// The size of the sub-file, in bytes /// public ulong Size; } /// /// Data store for the main File created for the AssetBundle /// [Serializable] public class File { /// /// The name of the File. /// public string Name; /// /// The AssetBundle data that relates to a built file. /// [SerializeReference] public Bundle Bundle; /// /// The file size of the AssetBundle on disk when uncompressed, in bytes /// public ulong UncompressedSize { get { ulong total = 0; foreach (SubFile subFile in SubFiles) total += subFile.Size; return total; } } /// /// List of the resource files created by the build pipeline that a File references /// [SerializeReference] public List SubFiles = new List(); /// /// A list of the explicit asset defined in the AssetBundle /// [SerializeReference] public List Assets = new List(); /// /// A list of implicit assets built into the AssetBundle, typically through references by Assets that are explicitly defined. /// [SerializeReference] public List OtherAssets = new List(); [SerializeReference] internal List ExternalReferences = new List(); /// /// The final filename of the AssetBundle file /// public string WriteResultFilename; /// /// Data about the AssetBundleObject /// public AssetBundleObjectInfo BundleObjectInfo; /// /// The size of the data that needs to be preloaded for this File. /// public int PreloadInfoSize; /// /// The number of Mono scripts referenced by the File /// public int MonoScriptCount; /// /// The size of the Mono scripts referenced by the File /// public ulong MonoScriptSize; } /// /// A representation of an object in an asset file. /// [Serializable] public class ObjectData { /// /// FileId of Object in Asset File /// public long LocalIdentifierInFile; /// /// Object name within the Asset /// [SerializeField] internal string ObjectName; /// /// Component name if AssetType is a MonoBehaviour or Component /// [SerializeField] internal string ComponentName; /// /// Type of Object /// public AssetType AssetType; /// /// The size of the file on disk. /// public ulong SerializedSize; /// /// The size of the streamed Asset. /// public ulong StreamedSize; /// /// References to other Objects /// [SerializeField] internal List References = new List(); } /// /// Identification of an Object within the same file /// [Serializable] internal class ObjectReference { public int AssetId; public List ObjectIds; } /// /// Data store for Assets explicitly defined in an AssetBundle /// [Serializable] public class ExplicitAsset { /// /// The Asset Guid. /// public string Guid; /// /// The Asset path on disk /// public string AssetPath; /// /// Name used to identify the asset within the asset bundle containing it /// public string InternalId; /// /// Hash of the asset content /// public Hash128 AssetHash; /// /// Objects that consist of the overall asset /// public List Objects = new List(); /// /// AssetType of the main Object for the Asset /// public AssetType MainAssetType; /// /// True if is a scene asset, else false /// public bool IsScene => AssetPath.EndsWith(".unity", StringComparison.Ordinal); /// /// Guid of the Addressable group this Asset entry was built using. /// public string GroupGuid; /// /// The Addressable address defined in the Addressable Group window for an Asset. /// public string AddressableName; /// /// Addressable labels for this asset entry. /// [SerializeField] public string[] Labels = Array.Empty(); /// /// The size of the file on disk. /// public ulong SerializedSize; /// /// The size of the streamed Asset. /// public ulong StreamedSize; /// /// The file that the Asset was added to /// [SerializeReference] public File File; /// /// The AssetBundle that contains the asset /// [SerializeReference] public Bundle Bundle; /// /// List of data from other Assets referenced by an Asset in the File /// [SerializeReference] public List InternalReferencedOtherAssets = new List(); /// /// List of explicit Assets referenced by this asset that are in the same AssetBundle /// [SerializeReference] public List InternalReferencedExplicitAssets = new List(); /// /// List of explicit Assets referenced by this asset that are in a different AssetBundle /// [SerializeReference] public List ExternallyReferencedAssets = new List(); /// /// List of Assets that reference this Asset /// [SerializeReference] internal List ReferencingAssets = new List(); } /// /// Data store for implicit Asset references /// [Serializable] public class DataFromOtherAsset { /// /// The Guid of the Asset /// public string AssetGuid; /// /// The Asset path on disk /// public string AssetPath; /// /// The file that the Asset was added to /// [SerializeReference] public File File; /// /// Objects that consist of the overall asset /// public List Objects = new List(); /// /// AssetType of the main Object for the Asset /// public AssetType MainAssetType; /// /// True if is a scene asset, else false /// public bool IsScene => AssetPath.EndsWith(".unity", StringComparison.Ordinal); /// /// A list of Assets that reference this data /// [SerializeReference] public List ReferencingAssets = new List(); /// /// The number of Objects in the data /// public int ObjectCount; /// /// The size of the data on disk /// public ulong SerializedSize; /// /// The size of the streamed data /// public ulong StreamedSize; } /// /// Data store for duplicated Implicit Asset information /// [Serializable] public class AssetDuplicationData { /// /// The Guid of the Asset with duplicates /// public string AssetGuid; /// /// A list of duplicated objects and the bundles that contain them. /// public List DuplicatedObjects = new List(); } /// /// Data store for duplicated Object information /// [Serializable] public class ObjectDuplicationData { /// /// The local identifier for an object. /// public long LocalIdentifierInFile; /// /// A list of bundles that include the referenced file. /// [SerializeReference] public List IncludedInBundleFiles = new List(); } } /// /// Utility used to quickly reference data built with the build pipeline /// public class LayoutLookupTables { /// /// The default AssetBundle name to the Bundle data map. /// public Dictionary Bundles = new Dictionary(); /// /// File name to File data map. /// public Dictionary Files = new Dictionary(); internal Dictionary FileToFileObjectData = new Dictionary(); /// /// Guid to ExplicitAsset data map. /// public Dictionary GuidToExplicitAsset = new Dictionary(); /// /// Group name to Group data map. /// public Dictionary GroupLookup = new Dictionary(); /// /// The remapped AssetBundle name to the Bundle data map /// internal Dictionary FilenameToBundle = new Dictionary(); /// Maps used for lookups while building the BuildLayout internal Dictionary> UsedImplicits = new Dictionary>(); internal Dictionary BundleNameToRequestOptions = new Dictionary(); internal Dictionary BundleNameToPreviousRequestOptions = new Dictionary(); internal Dictionary BundleNameToCatalogEntry = new Dictionary(); internal Dictionary GroupNameToBuildPath = new Dictionary(); internal Dictionary GuidToEntry = new Dictionary(); internal Dictionary AssetPathToTypeMap = new Dictionary(); } internal class FileObjectData { // id's for internal explicit asset and implicit asset public Dictionary InternalObjectIds = new Dictionary(); public Dictionary Objects = new Dictionary(); public void Add(ObjectIdentifier buildObjectIdentifier, BuildLayout.ObjectData layoutObject, int assetId, int objectIndex) { InternalObjectIds[buildObjectIdentifier] = (assetId, objectIndex); Objects[layoutObject] = buildObjectIdentifier; } public bool TryGetObjectReferenceData(ObjectIdentifier obj, out (int, int) value) { if (!InternalObjectIds.TryGetValue(obj, out (int, int) data)) { value = default; return false; } value = data; return true; } public bool TryGetObjectIdentifier(BuildLayout.ObjectData obj, out ObjectIdentifier objectIdOut) { if (!Objects.TryGetValue(obj, out objectIdOut)) { objectIdOut = default; return false; } return true; } } }