internal static bool IsCacheDataValid(AddressableAssetSettings settings, AddressablesContentState cacheData) { if (cacheData == null) { return(false); } if (cacheData.editorVersion != Application.unityVersion) { Addressables.LogWarningFormat("Building content update with Unity editor version `{0}`, data was created with version `{1}`. This may result in incompatible data.", Application.unityVersion, cacheData.editorVersion); } if (string.IsNullOrEmpty(cacheData.remoteCatalogLoadPath)) { Addressables.LogError("Previous build had 'Build Remote Catalog' disabled. You cannot update a player that has no remote catalog specified"); return(false); } if (!settings.BuildRemoteCatalog) { Addressables.LogError("Current settings have 'Build Remote Catalog' disabled. You cannot update a player that has no remote catalog to look to."); return(false); } if (cacheData.remoteCatalogLoadPath != settings.RemoteCatalogLoadPath.GetValue(settings)) { Addressables.LogErrorFormat("Current 'Remote Catalog Load Path' does not match load path of original player. Player will only know to look up catalog at original location. Original: {0} Current: {1}", cacheData.remoteCatalogLoadPath, settings.RemoteCatalogLoadPath.GetValue(settings)); return(false); } return(true); }
internal void SetAllValues(AddressableAssetSettings settings, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string playerBuildVersion) { AddressableSettings = settings; TargetGroup = buildTargetGroup; Target = buildTarget; PlayerVersion = playerBuildVersion; ProfilerEventsEnabled = ProjectConfigData.PostProfilerEvents; Registry = new FileRegistry(); PreviousContentState = null; }
/// <summary> /// Save the content update information for a set of AddressableAssetEntry objects. /// </summary> /// <param name="path">File to write content stat info to. If file already exists, it will be deleted before the new file is created.</param> /// <param name="entries">The entries to save.</param> /// <param name="dependencyData">The raw dependency information generated from the build.</param> /// <param name="playerVersion">The player version to save. This is usually set to AddressableAssetSettings.PlayerBuildVersion.</param> /// <param name="remoteCatalogPath">The server path (if any) that contains an updateable content catalog. If this is empty, updates cannot occur.</param> /// <returns>True if the file is saved, false otherwise.</returns> public static bool SaveContentState(string path, List <AddressableAssetEntry> entries, IDependencyData dependencyData, string playerVersion, string remoteCatalogPath) { try { IList <CachedAssetState> cachedInfos = new List <CachedAssetState>(); foreach (var assetData in dependencyData.AssetInfo) { CachedAssetState cachedAssetState; if (GetCachedAssetStateForData(assetData.Key, assetData.Value.referencedObjects.Select(x => x.guid), out cachedAssetState)) { cachedInfos.Add(cachedAssetState); } } foreach (var sceneData in dependencyData.SceneInfo) { CachedAssetState cachedAssetState; if (GetCachedAssetStateForData(sceneData.Key, sceneData.Value.referencedObjects.Select(x => x.guid), out cachedAssetState)) { cachedInfos.Add(cachedAssetState); } } var cacheData = new AddressablesContentState { cachedInfos = cachedInfos.ToArray(), playerVersion = playerVersion, editorVersion = Application.unityVersion, remoteCatalogLoadPath = remoteCatalogPath }; var formatter = new BinaryFormatter(); if (File.Exists(path)) { File.Delete(path); } var dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write); formatter.Serialize(stream, cacheData); stream.Flush(); stream.Close(); stream.Dispose(); return(true); } catch (Exception e) { Debug.LogException(e); return(false); } }
/// <summary> /// Get a Dictionary of all modified values and their dependencies. Dependencies will be Addressable and part of a group /// with static content enabled. /// </summary> /// <param name="settings">Addressable asset settings.</param> /// <param name="cachePath">The cache data path.</param> /// <returns>A dictionary mapping explicit changed entries to their dependencies.</returns> public static Dictionary <AddressableAssetEntry, List <AddressableAssetEntry> > GatherModifiedEntriesWithDependencies(AddressableAssetSettings settings, string cachePath) { var modifiedData = new Dictionary <AddressableAssetEntry, List <AddressableAssetEntry> >(); AddressablesContentState cacheData = LoadContentState(cachePath); if (cacheData == null) { return(modifiedData); } GatherExplicitModifiedEntries(settings, ref modifiedData, cacheData); GetStaticContentDependenciesForEntries(settings, ref modifiedData, GetGroupGuidToCacheBundleNameMap(cacheData)); return(modifiedData); }
internal static Dictionary <string, string> GetGroupGuidToCacheBundleNameMap(AddressablesContentState cacheData) { var bundleIdToCacheInfo = new Dictionary <string, string>(); foreach (CachedBundleState bundleInfo in cacheData.cachedBundles) { if (bundleInfo != null && bundleInfo.data is AssetBundleRequestOptions options) { bundleIdToCacheInfo[bundleInfo.bundleFileId] = options.BundleName; } } var groupGuidToCacheBundleName = new Dictionary <string, string>(); foreach (CachedAssetState cacheInfo in cacheData.cachedInfos) { if (cacheInfo != null && bundleIdToCacheInfo.TryGetValue(cacheInfo.bundleFileId, out string bundleName)) { groupGuidToCacheBundleName[cacheInfo.groupGuid] = bundleName; } } return(groupGuidToCacheBundleName); }
internal static void GatherExplicitModifiedEntries(AddressableAssetSettings settings, ref Dictionary <AddressableAssetEntry, List <AddressableAssetEntry> > dependencyMap, AddressablesContentState cacheData) { List <string> noBundledAssetGroupSchema = new List <string>(); List <string> noStaticContent = new List <string>(); var allEntries = new List <AddressableAssetEntry>(); settings.GetAllAssets(allEntries, false, g => { if (g == null) { return(false); } if (!g.HasSchema <BundledAssetGroupSchema>()) { noBundledAssetGroupSchema.Add(g.Name); return(false); } if (!g.HasSchema <ContentUpdateGroupSchema>()) { noStaticContent.Add(g.Name); return(false); } if (!g.GetSchema <ContentUpdateGroupSchema>().StaticContent) { noStaticContent.Add(g.Name); return(false); } return(true); }); StringBuilder builder = new StringBuilder(); builder.AppendFormat("Skipping Prepare for Content Update on {0} group(s):\n\n", noBundledAssetGroupSchema.Count + noStaticContent.Count); AddInvalidGroupsToLogMessage(builder, noBundledAssetGroupSchema, "Group Did Not Contain BundledAssetGroupSchema"); AddInvalidGroupsToLogMessage(builder, noStaticContent, "Static Content Not Enabled In Schemas"); Debug.Log(builder.ToString()); var entryToCacheInfo = new Dictionary <string, CachedAssetState>(); foreach (var cacheInfo in cacheData.cachedInfos) { if (cacheInfo != null) { entryToCacheInfo[cacheInfo.asset.guid.ToString()] = cacheInfo; } } var modifiedEntries = new List <AddressableAssetEntry>(); foreach (var entry in allEntries) { CachedAssetState cachedInfo; if (!entryToCacheInfo.TryGetValue(entry.guid, out cachedInfo) || HasAssetOrDependencyChanged(cachedInfo)) { modifiedEntries.Add(entry); } } AddAllDependentScenesFromModifiedEntries(modifiedEntries); foreach (var entry in modifiedEntries) { if (!dependencyMap.ContainsKey(entry)) { dependencyMap.Add(entry, new List <AddressableAssetEntry>()); } } }
/// <summary> /// Save the content update information for a set of AddressableAssetEntry objects. /// </summary> /// <param name="locations">The ContentCatalogDataEntry locations that were built into the Content Catalog.</param> /// <param name="path">File to write content stat info to. If file already exists, it will be deleted before the new file is created.</param> /// <param name="entries">The entries to save.</param> /// <param name="dependencyData">The raw dependency information generated from the build.</param> /// <param name="playerVersion">The player version to save. This is usually set to AddressableAssetSettings.PlayerBuildVersion.</param> /// <param name="remoteCatalogPath">The server path (if any) that contains an updateable content catalog. If this is empty, updates cannot occur.</param> /// <param name="carryOverCacheState">Cached state that needs to carry over from the previous build. This mainly affects Content Update.</param> /// <returns>True if the file is saved, false otherwise.</returns> public static bool SaveContentState(List <ContentCatalogDataEntry> locations, string path, List <AddressableAssetEntry> entries, IDependencyData dependencyData, string playerVersion, string remoteCatalogPath, List <CachedAssetState> carryOverCacheState) { try { var cachedInfos = GetCachedAssetStates(locations, entries, dependencyData); var cachedBundleInfos = new List <CachedBundleState>(); foreach (ContentCatalogDataEntry ccEntry in locations) { if (typeof(IAssetBundleResource).IsAssignableFrom(ccEntry.ResourceType)) { cachedBundleInfos.Add(new CachedBundleState() { bundleFileId = ccEntry.InternalId, data = ccEntry.Data }); } } if (carryOverCacheState != null) { foreach (var cs in carryOverCacheState) { cachedInfos.Add(cs); } } var cacheData = new AddressablesContentState { cachedInfos = cachedInfos.ToArray(), playerVersion = playerVersion, editorVersion = Application.unityVersion, remoteCatalogLoadPath = remoteCatalogPath, cachedBundles = cachedBundleInfos.ToArray() }; var formatter = new BinaryFormatter(); if (File.Exists(path)) { File.Delete(path); } var dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write); formatter.Serialize(stream, cacheData); stream.Flush(); stream.Close(); stream.Dispose(); return(true); } catch (UnauthorizedAccessException uae) { if (!AddressableAssetUtility.IsVCAssetOpenForEdit(path)) { Debug.LogErrorFormat("Cannot access the file {0}. It may be locked by version control.", path); } else { Debug.LogException(uae); } return(false); } catch (Exception e) { Debug.LogException(e); return(false); } }
/// <summary> /// Save the content update information for a set of AddressableAssetEntry objects. /// </summary> /// <param name="locations">The ContentCatalogDataEntry locations that were built into the Content Catalog.</param> /// <param name="path">File to write content stat info to. If file already exists, it will be deleted before the new file is created.</param> /// <param name="entries">The entries to save.</param> /// <param name="dependencyData">The raw dependency information generated from the build.</param> /// <param name="playerVersion">The player version to save. This is usually set to AddressableAssetSettings.PlayerBuildVersion.</param> /// <param name="remoteCatalogPath">The server path (if any) that contains an updateable content catalog. If this is empty, updates cannot occur.</param> /// <returns>True if the file is saved, false otherwise.</returns> public static bool SaveContentState(List <ContentCatalogDataEntry> locations, string path, List <AddressableAssetEntry> entries, IDependencyData dependencyData, string playerVersion, string remoteCatalogPath) { try { IList <CachedAssetState> cachedInfos = new List <CachedAssetState>(); foreach (var assetData in dependencyData.AssetInfo) { AddressableAssetEntry addressableAssetEntry = entries.FirstOrDefault((e) => e.guid == assetData.Key.ToString()); ContentCatalogDataEntry catalogAssetEntry = locations.FirstOrDefault((e) => { if (e.Keys.Count <= 1) { return(false); } return((e.Keys[1] as string) == assetData.Key.ToString()); }); CachedAssetState cachedAssetState; if (addressableAssetEntry != null && catalogAssetEntry != null && GetCachedAssetStateForData(assetData.Key, addressableAssetEntry.BundleFileId, addressableAssetEntry.parentGroup.Guid, catalogAssetEntry.Data, assetData.Value.referencedObjects.Select(x => x.guid), out cachedAssetState)) { cachedInfos.Add(cachedAssetState); } } foreach (var sceneData in dependencyData.SceneInfo) { AddressableAssetEntry addressableSceneEntry = entries.FirstOrDefault((e) => e.guid == sceneData.Key.ToString()); ContentCatalogDataEntry catalogSceneEntry = locations.FirstOrDefault((e) => { if (e.Keys.Count <= 1) { return(false); } return((e.Keys[1] as string) == sceneData.Key.ToString()); }); CachedAssetState cachedAssetState; if (addressableSceneEntry != null && catalogSceneEntry != null && GetCachedAssetStateForData(sceneData.Key, addressableSceneEntry.BundleFileId, addressableSceneEntry.parentGroup.Guid, catalogSceneEntry.Data, sceneData.Value.referencedObjects.Select(x => x.guid), out cachedAssetState)) { cachedInfos.Add(cachedAssetState); } } var cacheData = new AddressablesContentState { cachedInfos = cachedInfos.ToArray(), playerVersion = playerVersion, editorVersion = Application.unityVersion, remoteCatalogLoadPath = remoteCatalogPath }; var formatter = new BinaryFormatter(); if (File.Exists(path)) { File.Delete(path); } var dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write); formatter.Serialize(stream, cacheData); stream.Flush(); stream.Close(); stream.Dispose(); return(true); } catch (Exception e) { Debug.LogException(e); return(false); } }
/// <summary> /// Save the content update information for a set of AddressableAssetEntry objects. /// </summary> /// <param name="locations">The ContentCatalogDataEntry locations that were built into the Content Catalog.</param> /// <param name="path">File to write content stat info to. If file already exists, it will be deleted before the new file is created.</param> /// <param name="entries">The entries to save.</param> /// <param name="dependencyData">The raw dependency information generated from the build.</param> /// <param name="playerVersion">The player version to save. This is usually set to AddressableAssetSettings.PlayerBuildVersion.</param> /// <param name="remoteCatalogPath">The server path (if any) that contains an updateable content catalog. If this is empty, updates cannot occur.</param> /// <param name="carryOverCacheState">Cached state that needs to carry over from the previous build. This mainly affects Content Update.</param> /// <returns>True if the file is saved, false otherwise.</returns> public static bool SaveContentState(List <ContentCatalogDataEntry> locations, string path, List <AddressableAssetEntry> entries, IDependencyData dependencyData, string playerVersion, string remoteCatalogPath, List <CachedAssetState> carryOverCacheState) { try { Dictionary <string, AddressableAssetEntry> guidToEntries = new Dictionary <string, AddressableAssetEntry>(); Dictionary <string, ContentCatalogDataEntry> key1ToCCEntries = new Dictionary <string, ContentCatalogDataEntry>(); foreach (AddressableAssetEntry entry in entries) { if (!guidToEntries.ContainsKey(entry.guid)) { guidToEntries[entry.guid] = entry; } } foreach (ContentCatalogDataEntry ccEntry in locations) { if (ccEntry != null && ccEntry.Keys != null && ccEntry.Keys.Count > 1 && (ccEntry.Keys[1] as string) != null && !key1ToCCEntries.ContainsKey(ccEntry.Keys[1] as string)) { key1ToCCEntries[ccEntry.Keys[1] as string] = ccEntry; } } IList <CachedAssetState> cachedInfos = new List <CachedAssetState>(); foreach (var assetData in dependencyData.AssetInfo) { guidToEntries.TryGetValue(assetData.Key.ToString(), out AddressableAssetEntry addressableAssetEntry); key1ToCCEntries.TryGetValue(assetData.Key.ToString(), out ContentCatalogDataEntry catalogAssetEntry); if (addressableAssetEntry != null && catalogAssetEntry != null && GetCachedAssetStateForData(assetData.Key, addressableAssetEntry.BundleFileId, addressableAssetEntry.parentGroup.Guid, catalogAssetEntry.Data, assetData.Value.referencedObjects.Select(x => x.guid), out CachedAssetState cachedAssetState)) { cachedInfos.Add(cachedAssetState); } } foreach (var sceneData in dependencyData.SceneInfo) { guidToEntries.TryGetValue(sceneData.Key.ToString(), out AddressableAssetEntry addressableSceneEntry); key1ToCCEntries.TryGetValue(sceneData.Key.ToString(), out ContentCatalogDataEntry catalogSceneEntry); if (addressableSceneEntry != null && catalogSceneEntry != null && GetCachedAssetStateForData(sceneData.Key, addressableSceneEntry.BundleFileId, addressableSceneEntry.parentGroup.Guid, catalogSceneEntry.Data, sceneData.Value.referencedObjects.Select(x => x.guid), out CachedAssetState cachedAssetState)) { cachedInfos.Add(cachedAssetState); } } if (carryOverCacheState != null) { foreach (var cs in carryOverCacheState) { cachedInfos.Add(cs); } } var cacheData = new AddressablesContentState { cachedInfos = cachedInfos.ToArray(), playerVersion = playerVersion, editorVersion = Application.unityVersion, remoteCatalogLoadPath = remoteCatalogPath }; var formatter = new BinaryFormatter(); if (File.Exists(path)) { File.Delete(path); } var dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write); formatter.Serialize(stream, cacheData); stream.Flush(); stream.Close(); stream.Dispose(); return(true); } catch (Exception e) { Debug.LogException(e); return(false); } }
internal static void GetStaticContentDependenciesForEntries(AddressableAssetSettings settings, ref Dictionary <AddressableAssetEntry, List <AddressableAssetEntry> > dependencyMap, AddressablesContentState cacheData = null) { Dictionary <AddressableAssetGroup, bool> groupHasStaticContentMap = new Dictionary <AddressableAssetGroup, bool>(); if (dependencyMap == null) { return; } HashSet <string> groupGuidsWithUnchangedBundleName = GetGroupGuidsWithUnchangedBundleName(settings, dependencyMap, cacheData); foreach (AddressableAssetEntry entry in dependencyMap.Keys) { //since the entry here is from our list of modified entries we know that it must be a part of a static content group. //Since it's part of a static content update group we can go ahead and set the value to true in the dictionary without explicitly checking it. if (!groupHasStaticContentMap.ContainsKey(entry.parentGroup)) { groupHasStaticContentMap.Add(entry.parentGroup, true); } string[] dependencies = AssetDatabase.GetDependencies(entry.AssetPath); foreach (string dependency in dependencies) { string guid = AssetDatabase.AssetPathToGUID(dependency); var depEntry = settings.FindAssetEntry(guid); if (depEntry == null) { continue; } if (!groupHasStaticContentMap.TryGetValue(depEntry.parentGroup, out bool groupHasStaticContentEnabled)) { groupHasStaticContentEnabled = depEntry.parentGroup.HasSchema <ContentUpdateGroupSchema>() && depEntry.parentGroup.GetSchema <ContentUpdateGroupSchema>().StaticContent; if (groupGuidsWithUnchangedBundleName.Contains(depEntry.parentGroup.Guid)) { continue; } groupHasStaticContentMap.Add(depEntry.parentGroup, groupHasStaticContentEnabled); } if (!dependencyMap.ContainsKey(depEntry) && groupHasStaticContentEnabled) { if (!dependencyMap.ContainsKey(entry)) { dependencyMap.Add(entry, new List <AddressableAssetEntry>()); } dependencyMap[entry].Add(depEntry); } } } }
internal static HashSet <string> GetGroupGuidsWithUnchangedBundleName(AddressableAssetSettings settings, Dictionary <AddressableAssetEntry, List <AddressableAssetEntry> > dependencyMap, AddressablesContentState cacheData) { var result = new HashSet <string>(); if (cacheData == null) { return(result); } Dictionary <string, string> groupGuidToCacheBundleName = GetGroupGuidToCacheBundleNameMap(cacheData); foreach (AddressableAssetGroup group in settings.groups) { if (group == null || !group.HasSchema <BundledAssetGroupSchema>()) { continue; } var schema = group.GetSchema <BundledAssetGroupSchema>(); List <AssetBundleBuild> bundleInputDefinitions = new List <AssetBundleBuild>(); BuildScriptPackedMode.PrepGroupBundlePacking(group, bundleInputDefinitions, schema, x => !dependencyMap.ContainsKey(x)); BuildScriptPackedMode.HandleDuplicateBundleNames(bundleInputDefinitions); for (int i = 0; i < bundleInputDefinitions.Count; i++) { string bundleName = Path.GetFileNameWithoutExtension(bundleInputDefinitions[i].assetBundleName); if (groupGuidToCacheBundleName.TryGetValue(group.Guid, out string cacheBundleName) && cacheBundleName == bundleName) { result.Add(group.Guid); } } } return(result); }