From e45390ba47ea23ec7b2f468efc9fb9bc856daf27 Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Wed, 10 Mar 2021 00:00:00 +0000 Subject: [PATCH] com.unity.addressables@1.17.13 ## [1.17.13] - 2021-03-10 - Fixed issue when loading a Sprite from a SpriteAtlas from an Addressable folder in AssetDatabase mode. - Fixed bug in AssetReference "Make Addressable" functionality (when referencing an asset no longer addressable) - Fixed bug with cyclic references in profile variable causing an infinite loop. - Fixed bug where cached asset type could get stuck with DefaultType, an invalid Editor type - Fixed issue where AsyncOperationHandle.Completed is called after AsyncOperationHandle.Task returns when the handle is already done. - Fixed some faulty logic in GetDownloadStatus() when errors occur - Removed extra dependencies that were being flagged as modified when running Check For Content Update Restrictions. - Fixed a bug where the result of a Task could be inconsistent and return null given certain race conditions - Fixed bug where UnloadSceneAsync decreased ref count more than once, and added unload scene to Release if ref count goes to zero - Fixed issue where a popup appears when an AddressableAsset file is being modified even if the file is checked out locally. - Fixed bug where fast mode wasn't showing events in the profiler - Remove check for isUpdating and isCompiling so GetSettings(true) still tries to load the settings when compiling or updating - Fixed issue where modified local static bundle dependencies fail to load after updating a previous build. Fix is compatible with older shipped content. --- CHANGELOG.md | 18 + Documentation~/ContentUpdateWorkflow.md | 4 +- Documentation~/SynchronousAddressables.md | 4 + Documentation~/TableOfContents.md | 40 +-- .../AddressableAssetSettingsDefaultObject.cs | 10 +- .../Build/AddressableAssetSettingsLocator.cs | 19 +- Editor/Build/AnalyzeRules/AnalyzeSystem.cs | 2 +- Editor/Build/AnalyzeRules/BundleRuleBase.cs | 23 +- .../BuildLayoutGenerationTask.cs | 10 +- Editor/Build/ContentUpdateScript.cs | 109 ++++-- Editor/Build/DataBuilders/BuildScriptBase.cs | 2 +- .../DataBuilders/BuildScriptPackedMode.cs | 40 ++- .../DataBuilders/BuildScriptVirtualMode.cs | 2 +- .../Build/FastModeInitializationOperation.cs | 10 + ...vertUnchangedAssetsToPreviousAssetState.cs | 15 +- Editor/Build/SceneManagerState.cs | 14 +- Editor/GUI/AssetReferenceDrawer.cs | 10 +- Editor/Settings/AddressableAssetEntry.cs | 12 +- Editor/Settings/AddressableAssetGroup.cs | 39 ++- Editor/Settings/AddressableAssetSettings.cs | 2 +- Editor/Settings/AddressableAssetUtility.cs | 78 +++-- Editor/Settings/Preferences.cs | 2 +- Runtime/Addressables.cs | 3 + Runtime/AddressablesImpl.cs | 16 +- .../AddressablesRuntimeProperties.cs | 67 +++- .../DynamicResourceLocators.cs | 28 +- .../AsyncOperations/AsyncOperationBase.cs | 54 ++- .../AsyncOperations/ChainOperation.cs | 34 +- .../AsyncOperations/ProviderOperation.cs | 5 +- .../ResourceProviders/AssetBundleProvider.cs | 21 +- .../ResourceProviders/SceneProvider.cs | 9 +- Runtime/Services/PlatformMappingService.cs | 33 +- Tests/Editor/AddressableAssetEntryTests.cs | 25 ++ .../AddressableAssetSettingsLocatorTests.cs | 6 +- Tests/Editor/AddressableAssetSettingsTests.cs | 2 +- .../CheckSceneDupeDependenciesTests.cs | 33 ++ .../Build/BuildLayoutGenerationTaskTests.cs | 8 +- Tests/Editor/Build/BuildScriptPackedTests.cs | 2 +- Tests/Editor/Build/BuildScriptTests.cs | 7 +- Tests/Editor/ContentUpdateTests.cs | 311 ++++++++++++++---- Tests/Editor/GroupSchemaTests.cs | 1 + Tests/Runtime/AddressablesIntegrationTests.cs | 4 +- .../AddressablesIntegrationTestsImpl.cs | 143 +++++--- Tests/Runtime/AddressablesTestFixture.cs | 17 +- Tests/Runtime/AddressablesTestUtilities.cs | 58 +++- Tests/Runtime/AsyncTaskTests.cs | 51 +++ Tests/Runtime/AsyncTaskTests.cs.meta | 11 + .../AddrRuntimePropertiesTests.cs | 45 +++ .../FastModeInitializationTests.cs | 58 ++++ .../FastModeInitializationTests.cs.meta | 11 + .../InitializationObjectsAsyncTests.cs | 5 - .../Operations/BaseOperationBehaviorTests.cs | 30 ++ .../ResourceManagerBaseTests.cs | 32 +- Tests/Runtime/SceneTests.cs | 146 +++++++- package.json | 8 +- 55 files changed, 1441 insertions(+), 308 deletions(-) create mode 100644 Tests/Runtime/AsyncTaskTests.cs create mode 100644 Tests/Runtime/AsyncTaskTests.cs.meta create mode 100644 Tests/Runtime/Initialization/FastModeInitializationTests.cs create mode 100644 Tests/Runtime/Initialization/FastModeInitializationTests.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index c7145f31..772bd24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.17.13] - 2021-03-10 +- Fixed issue when loading a Sprite from a SpriteAtlas from an Addressable folder in AssetDatabase mode. +- Fixed bug in AssetReference "Make Addressable" functionality (when referencing an asset no longer addressable) +- Fixed bug with cyclic references in profile variable causing an infinite loop. +- Fixed bug where cached asset type could get stuck with DefaultType, an invalid Editor type +- Fixed issue where AsyncOperationHandle.Completed is called after AsyncOperationHandle.Task returns when the handle is already done. +- Fixed some faulty logic in GetDownloadStatus() when errors occur +- Removed extra dependencies that were being flagged as modified when running Check For Content Update Restrictions. +- Fixed a bug where the result of a Task could be inconsistent and return null given certain race conditions +- Fixed bug where UnloadSceneAsync decreased ref count more than once, and added unload scene to Release if ref count goes to zero +- Fixed issue where a popup appears when an AddressableAsset file is being modified even if the file is checked out locally. +- Fixed bug where fast mode wasn't showing events in the profiler +- Remove check for isUpdating and isCompiling so GetSettings(true) still tries to load the settings when compiling or updating +- Fixed issue where modified local static bundle dependencies fail to load after updating a previous build. Fix is compatible with older shipped content. + ## [1.17.6-preview] - 2021-02-23 - Fixed issue where OnGlobalModification events would be EntryMoved when adding new Entries instead of EntryAdded. - Fixed issue where a previously built player fails to load content after running Content Update with missing local bundles @@ -79,6 +94,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The "Ignore Invalid/Unsupported Files" option is now saved in the settings - Fixed issue where Filename only bundle naming schemas were overwriting old bundles prematurely in content update. +## [1.16.17] - 2021-02-25 +- Updated group rename logic to support engine AssetDatabase fix. Change should be transparent to users. + ## [1.16.16] - 2021-01-20 - Updated dependency versions for testcase fix diff --git a/Documentation~/ContentUpdateWorkflow.md b/Documentation~/ContentUpdateWorkflow.md index 73f696dc..070ea9bf 100644 --- a/Documentation~/ContentUpdateWorkflow.md +++ b/Documentation~/ContentUpdateWorkflow.md @@ -68,13 +68,13 @@ When loading AssetBundles into memory, Unity enforces that two bundles cannot be To make this work, one of two things must happen. One option is to unload all your Addressables content prior to updating the catalog. This ensures new bundles with old names will not cause conflicts in memory. The second option is to ensure that your updated AssetBundles have unique internal identifiers. This would allow you to load new bundles, while the old are still in memory. We have an option to enable this second option. Turn on "Unique Bundle IDs" within the [`AddressableAssetSettings`](xref:UnityEditor.AddressableAssets.Settings.AddressableAssetSettings) Inspector. The downside of this option is that it requires bundles to be rebuilt up the dependency chain. Meaning if you changed a material in one group, by default only the material's bundle would be rebuilt. With "Unique Bundle IDs" on, any Asset that references that material would also need rebuilding. ## Identifying changed assets -If you have modified Assets in any `Cannot Change Post Release` groups, you'll need to run the **Check for Content Update Restrictions** command (step 5 above). This will take any modified Asset, all of its dependencies, and all other Assets that depend on the modified Asset out of the `Cannot Change Post Release` groups and move them to a new group. To generate the new Asset groups: +If you have modified Assets in any `Cannot Change Post Release` groups, you'll need to run the **Check for Content Update Restrictions** command (step 5 above). This will take any modified Asset, its dependencies if their bundle name was modified, and all other Assets that depend on the modified Asset out of the `Cannot Change Post Release` groups and move them to a new group. To generate the new Asset groups: 1. Open the **Addressables Groups** window in the Unity Editor (**Window** > **Asset Management** > **Addressables** > **Groups**). 2. In the **Addressables Groups** window, select **Tools** on the top menu bar, then **Check for Content Update Restrictions**. 3. In the **Build Data File** dialog that opens, select the _addressables_content_state.bin_ file (by default, this is located in the `Assets/AddressableAssetsData/\` Project directory, where `\` is your target platform). -This data is used to determine which Assets or dependencies have been modified since the application was last built. The system moves these Assets, their dependencies, and all other Assets that depend on the modified Assets to a new group in preparation for the content update build. +This data is used to determine which Assets or dependencies have been modified since the application was last built. The system moves these Assets, their dependencies if their bundle name was modified, and all other Assets that depend on the modified Assets to a new group in preparation for the content update build. **Note**: This command will do nothing if all your changes are confined to `Can Change Post Release` groups. diff --git a/Documentation~/SynchronousAddressables.md b/Documentation~/SynchronousAddressables.md index e183fe21..32206b31 100644 --- a/Documentation~/SynchronousAddressables.md +++ b/Documentation~/SynchronousAddressables.md @@ -12,6 +12,10 @@ It is possible to get a `default(TObject)` for a result when the operation doesn ## Performance It is worth noting that calling `WaitForCompletion` may have performance implications on your runtime when compared to `Resources.Load` or `Instantiate` calls directly. If your `AssetBundle` is local or has been previously downloaded and cached, these performance hits are likely to be negligible. However, this may not be the case for your individual project setup. +All currently active Asset Load operations are completed when `WaitForCompletion` is called on any Asset Load operation, due to how Async operations are handled in the Engine. To avoid unexpected stalls, use `WaitForCompletion` when the current operation count is known, and the intention is for all active operations to complete synchronously. + +When using `WaitForCompletion`, there are performance implications. When using 2021.2.0 or newer, these are minimal. Using an older version can result in delays that scale with the number of Engine Asset load calls that are loading when `WaitForCompletion` is called. + It is not recommended that you call `WaitForCompletion` on an operation that is going to fetch and download a remote `AssetBundle`. Though, it is possible if that fits your specific situation. ## Code Sample diff --git a/Documentation~/TableOfContents.md b/Documentation~/TableOfContents.md index 9d30acbc..74246226 100644 --- a/Documentation~/TableOfContents.md +++ b/Documentation~/TableOfContents.md @@ -26,6 +26,7 @@ * [Profile Setup](AddressableAssetsProfiles.md#profile-setup) * [Specifying packing and loading paths](AddressableAssetsProfiles.md#specifying-packing-and-loading-paths) * [Examples](AddressableAssetsProfiles.md#examples) + * [Synchronous Addressables](SynchronousAddressables.md) * [Asset Hosting Services](AddressableAssetsHostingServices.md) * [Overview](AddressableAssetsHostingServices.md#overview) * [Setup](AddressableAssetsHostingServices.md#setup) @@ -44,18 +45,6 @@ * [The direct reference method](AddressableAssetsMigrationGuide.md#the-direct-reference-method) * [The Resource folders method](AddressableAssetsMigrationGuide.md#the-resource-folders-method) * [The AssetBundles method](AddressableAssetsMigrationGuide.md#the-assetbundles-method) - * [Expanded API documentation](AddressablesAPI.md) - * [BuildPlayerContent](BuildPlayerContent.md) - * [DownloadDependenciesAsync](DownloadDependenciesAsync.md) - * [ExceptionHandler](ExceptionHandler.md) - * [InitializeAsync](InitializeAsync.md) - * [InstantiateAsync](InstantiateAsync.md) - * [LoadContentCatalogAsync](LoadContentCatalogAsync.md) - * [LoadingAddressableAssets](LoadingAddressableAssets.md) - * [LoadResourceLocationsAsync](LoadResourceLocations.md) - * [LoadSceneAsync](LoadSceneAsync.md) - * [TransformInternalId](TransformInternalId.md) - * [UpdateCatalogs](UpdateCatalogs.md) * [Diagnostic Tools](DiagnosticTools.md) * [Build Layout](DiagnosticTools.md#build-layout-report) * [Build Profiling](DiagnosticTools.md#build-profiling) @@ -63,11 +52,22 @@ * [Using Analyze](DiagnosticTools.md#using-analyze) * [Provided Analyze rules](DiagnosticTools.md#provided-analyze-rules) * [Extending Analyze](DiagnosticTools.md#extending-analyze) - * [FAQ](AddressablesFAQ.md) - * [Many vs few bundles](AddressablesFAQ.md#Is-it-better-to-have-many-small-bundles-or-a-few-bigger-ones) - * [Best compression settings](AddressablesFAQ.md#What-compression-settings-are-best) - * [Minimize catlog size](AddressablesFAQ.md#Are-there-ways-to-miminize-the-catalog-size) - * [Addressables content state](AddressablesFAQ.md#What-is-addressables_content_state) - * [Scale implications](AddressablesFAQ.md#What-are-possible-scale-implications) - * [Synchronous Addressables](SynchronousAddressables.md) - + * [FAQ](AddressablesFAQ.md) + * [Many vs few bundles](AddressablesFAQ.md#Is-it-better-to-have-many-small-bundles-or-a-few-bigger-ones) + * [Best compression settings](AddressablesFAQ.md#What-compression-settings-are-best) + * [Minimize catlog size](AddressablesFAQ.md#Are-there-ways-to-miminize-the-catalog-size) + * [Addressables content state](AddressablesFAQ.md#What-is-addressables_content_state) + * [Scale implications](AddressablesFAQ.md#What-are-possible-scale-implications) + * [Address Lookup](AddressablesFAQ.md#is-it-possible-to-retrieve-the-address-of-an-asset-or-reference-at-runtime) +* [Expanded API documentation](AddressablesAPI.md) + * [BuildPlayerContent](BuildPlayerContent.md) + * [DownloadDependenciesAsync](DownloadDependenciesAsync.md) + * [ExceptionHandler](ExceptionHandler.md) + * [InitializeAsync](InitializeAsync.md) + * [InstantiateAsync](InstantiateAsync.md) + * [LoadContentCatalogAsync](LoadContentCatalogAsync.md) + * [LoadingAddressableAssets](LoadingAddressableAssets.md) + * [LoadResourceLocationsAsync](LoadResourceLocations.md) + * [LoadSceneAsync](LoadSceneAsync.md) + * [TransformInternalId](TransformInternalId.md) + * [UpdateCatalogs](UpdateCatalogs.md) diff --git a/Editor/AddressableAssetSettingsDefaultObject.cs b/Editor/AddressableAssetSettingsDefaultObject.cs index 3b2a0c90..d72801b2 100644 --- a/Editor/AddressableAssetSettingsDefaultObject.cs +++ b/Editor/AddressableAssetSettingsDefaultObject.cs @@ -22,10 +22,6 @@ public class AddressableAssetSettingsDefaultObject : ScriptableObject /// The name of the default config object /// public const string kDefaultConfigObjectName = "com.unity.addressableassets"; - /// - /// The path to the Library folder for storing Addressables data - /// - internal const string kAddressablesLibraryPath = "Library/com.unity.addressables"; /// /// Default path for addressable asset settings assets. @@ -110,7 +106,7 @@ public static AddressableAssetSettings Settings { get { - if (s_DefaultSettingsObject == null && !EditorApplication.isUpdating && !EditorApplication.isCompiling) + if (s_DefaultSettingsObject == null) { AddressableAssetSettingsDefaultObject so; if (EditorBuildSettings.TryGetConfigObject(kDefaultConfigObjectName, out so)) @@ -127,7 +123,7 @@ public static AddressableAssetSettings Settings so.SetSettingsObject(s_DefaultSettingsObject); AssetDatabase.CreateAsset(so, kDefaultConfigFolder + "/DefaultObject.asset"); EditorUtility.SetDirty(so); - AddressableAssetUtility.OpenAssetIfUsingVCIntegration(so, kDefaultConfigFolder + "/DefaultObject.asset"); + AddressableAssetUtility.OpenAssetIfUsingVCIntegration(kDefaultConfigFolder + "/DefaultObject.asset"); AssetDatabase.SaveAssets(); EditorBuildSettings.AddConfigObject(kDefaultConfigObjectName, so, true); } @@ -158,7 +154,7 @@ public static AddressableAssetSettings Settings } so.SetSettingsObject(s_DefaultSettingsObject); EditorUtility.SetDirty(so); - AddressableAssetUtility.OpenAssetIfUsingVCIntegration(so, kDefaultConfigFolder + "/DefaultObject.asset"); + AddressableAssetUtility.OpenAssetIfUsingVCIntegration(kDefaultConfigFolder + "/DefaultObject.asset"); AssetDatabase.SaveAssets(); } } diff --git a/Editor/Build/AddressableAssetSettingsLocator.cs b/Editor/Build/AddressableAssetSettingsLocator.cs index 72857ca2..f33b609a 100644 --- a/Editor/Build/AddressableAssetSettingsLocator.cs +++ b/Editor/Build/AddressableAssetSettingsLocator.cs @@ -6,19 +6,23 @@ using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; +using UnityEngine.U2D; using static UnityEditor.AddressableAssets.Settings.AddressablesFileEnumeration; namespace UnityEditor.AddressableAssets.Settings { internal class AddressableAssetSettingsLocator : IResourceLocator { + private static Type m_SpriteType = typeof(Sprite); + private static Type m_SpriteAtlasType = typeof(SpriteAtlas); + public string LocatorId { get; private set; } public Dictionary> m_keyToEntries; public Dictionary> m_Cache; public AddressableAssetTree m_AddressableAssetTree; HashSet m_Keys = null; AddressableAssetSettings m_Settings; - + public IEnumerable Keys { get @@ -258,7 +262,11 @@ public bool Locate(object key, Type type, out IList locations if (m_keyToEntries.ContainsKey(parentFolderKey)) { - locations.Add(new ResourceLocationBase(keyPath, AssetDatabase.GUIDToAssetPath(keyStr), typeof(AssetDatabaseProvider).FullName, type)); + string keyAssetPath = AssetDatabase.GUIDToAssetPath(keyStr); + if (type == m_SpriteType && AssetDatabase.GetMainAssetTypeAtPath(keyAssetPath) == m_SpriteAtlasType) + locations.Add(new ResourceLocationBase(keyPath, keyAssetPath, typeof(AssetDatabaseProvider).FullName, m_SpriteAtlasType)); + else + locations.Add(new ResourceLocationBase(keyPath, keyAssetPath, typeof(AssetDatabaseProvider).FullName, type)); break; } slash = keyPath.LastIndexOf('/'); @@ -279,7 +287,12 @@ public bool Locate(object key, Type type, out IList locations { var internalId = GetInternalIdFromFolderEntry(keyStr, e); if (!string.IsNullOrEmpty(internalId) && !string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(internalId))) - locations.Add(new ResourceLocationBase(keyStr, internalId, typeof(AssetDatabaseProvider).FullName, type)); + { + if (type == m_SpriteType && AssetDatabase.GetMainAssetTypeAtPath(internalId) == m_SpriteAtlasType) + locations.Add(new ResourceLocationBase(keyStr, internalId, typeof(AssetDatabaseProvider).FullName, m_SpriteAtlasType)); + else + locations.Add(new ResourceLocationBase(keyStr, internalId, typeof(AssetDatabaseProvider).FullName, type)); + } } break; } diff --git a/Editor/Build/AnalyzeRules/AnalyzeSystem.cs b/Editor/Build/AnalyzeRules/AnalyzeSystem.cs index 99ff9365..ecb166dc 100644 --- a/Editor/Build/AnalyzeRules/AnalyzeSystem.cs +++ b/Editor/Build/AnalyzeRules/AnalyzeSystem.cs @@ -47,7 +47,7 @@ internal static string AnalyzeRuleDataFolder { get { - return $"{AddressableAssetSettingsDefaultObject.kAddressablesLibraryPath}/AnalyzeData"; + return $"{Addressables.LibraryPath}/AnalyzeData"; } } diff --git a/Editor/Build/AnalyzeRules/BundleRuleBase.cs b/Editor/Build/AnalyzeRules/BundleRuleBase.cs index 83c04ef2..61e45ca0 100644 --- a/Editor/Build/AnalyzeRules/BundleRuleBase.cs +++ b/Editor/Build/AnalyzeRules/BundleRuleBase.cs @@ -9,7 +9,6 @@ using UnityEditor.Build.Pipeline; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline.Tasks; -using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.AddressableAssets.Initialization; using UnityEngine.AddressableAssets.ResourceLocators; @@ -137,10 +136,16 @@ internal virtual void BuiltInResourcesToDependenciesMap(string[] resourcePaths) string[] dependencies = AssetDatabase.GetDependencies(path); if (!m_ResourcesToDependencies.ContainsKey(path)) - m_ResourcesToDependencies.Add(path, new List()); - - m_ResourcesToDependencies[path].AddRange(from dependency in dependencies - select new GUID(AssetDatabase.AssetPathToGUID(dependency))); + m_ResourcesToDependencies.Add(path, new List(dependencies.Length)); + else + m_ResourcesToDependencies[path].Capacity += dependencies.Length; + + foreach (string dependency in dependencies) + { + if (dependency.EndsWith(".cs") || dependency.EndsWith(".dll")) + continue; + m_ResourcesToDependencies[path].Add(new GUID(AssetDatabase.AssetPathToGUID(dependency))); + } } } @@ -204,12 +209,16 @@ internal void CalculateInputDefinitions(AddressableAssetSettings settings) } } } - internal AssetBundleBuild CreateUniqueBundle(AssetBundleBuild bid) + { + return CreateUniqueBundle(bid, m_BundleToAssetGroup); + } + + internal static AssetBundleBuild CreateUniqueBundle(AssetBundleBuild bid, Dictionary bundleToAssetGroup) { int count = 1; var newName = bid.assetBundleName; - while (m_BundleToAssetGroup.ContainsKey(newName) && count < 1000) + while (bundleToAssetGroup.ContainsKey(newName) && count < 1000) newName = bid.assetBundleName.Replace(".bundle", string.Format("{0}.bundle", count++)); return new AssetBundleBuild { diff --git a/Editor/Build/BuildPipelineTasks/BuildLayoutGenerationTask.cs b/Editor/Build/BuildPipelineTasks/BuildLayoutGenerationTask.cs index 14d01f94..7a350dd3 100644 --- a/Editor/Build/BuildPipelineTasks/BuildLayoutGenerationTask.cs +++ b/Editor/Build/BuildPipelineTasks/BuildLayoutGenerationTask.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; using UnityEditor.AddressableAssets.Build.DataBuilders; using UnityEditor.AddressableAssets.Build.Layout; using UnityEditor.AddressableAssets.Settings; @@ -11,6 +10,7 @@ using UnityEditor.Build.Pipeline; using UnityEditor.Build.Pipeline.Injector; using UnityEditor.Build.Pipeline.Interfaces; +using UnityEngine.AddressableAssets; namespace UnityEditor.AddressableAssets.Build.BuildPipelineTasks { @@ -58,7 +58,7 @@ public class BuildLayoutGenerationTask : IBuildTask internal Dictionary m_BundleNameRemap; - internal const string kLayoutTextFile = "Library/com.unity.addressables/buildlayout.txt"; + internal static string m_LayoutTextFile = Addressables.LibraryPath + "/buildlayout.txt"; static AssetBucket GetOrCreate(Dictionary buckets, string asset) { @@ -312,11 +312,11 @@ public ReturnCode Run() { BuildLayout layout = CreateBuildLayout(); - Directory.CreateDirectory(Path.GetDirectoryName(kLayoutTextFile)); - using (FileStream s = File.Open(kLayoutTextFile, FileMode.Create)) + Directory.CreateDirectory(Path.GetDirectoryName(m_LayoutTextFile)); + using (FileStream s = File.Open(m_LayoutTextFile, FileMode.Create)) BuildLayoutPrinter.WriteBundleLayout(s, layout); - UnityEngine.Debug.Log($"Build layout written to {kLayoutTextFile}"); + UnityEngine.Debug.Log($"Build layout written to {m_LayoutTextFile}"); s_LayoutCompleteCallback?.Invoke(layout); diff --git a/Editor/Build/ContentUpdateScript.cs b/Editor/Build/ContentUpdateScript.cs index e6a1f8b8..ef7237a0 100644 --- a/Editor/Build/ContentUpdateScript.cs +++ b/Editor/Build/ContentUpdateScript.cs @@ -335,7 +335,7 @@ public static bool SaveContentState(List locations, str } catch (UnauthorizedAccessException uae) { - if (AddressableAssetUtility.IsUsingVCIntegration()) + if (!AddressableAssetUtility.IsVCAssetOpenForEdit(path)) Debug.LogErrorFormat("Cannot access the file {0}. Is it checked out?", path); else Debug.LogException(uae); @@ -391,7 +391,7 @@ public static string GetContentStateDataPath(bool browse) { string assetPath = AddressableAssetSettingsDefaultObject.Settings != null ? AddressableAssetSettingsDefaultObject.Settings.GetContentStateBuildPath() : - Path.Combine(AddressableAssetSettingsDefaultObject.kDefaultConfigFolder, PlatformMappingService.GetPlatform().ToString()); + Path.Combine(AddressableAssetSettingsDefaultObject.kDefaultConfigFolder, PlatformMappingService.GetPlatformPathSubFolder()); if (browse) { @@ -416,7 +416,7 @@ public static string GetContentStateDataPath(bool browse) { Debug.LogError(e.Message + "\nCheck \"Content State Build Path\" in Addressables settings. Falling back to config folder location."); assetPath = Path.Combine(AddressableAssetSettingsDefaultObject.kDefaultConfigFolder, - PlatformMappingService.GetPlatform().ToString()); + PlatformMappingService.GetPlatformPathSubFolder()); Directory.CreateDirectory(assetPath); } } @@ -544,15 +544,8 @@ public static List GatherModifiedEntries(AddressableAsset return retVal.ToList(); } - internal static void GatherExplicitModifiedEntries(AddressableAssetSettings settings, string cacheDataPath, ref Dictionary> dependencyMap) + internal static void GatherExplicitModifiedEntries(AddressableAssetSettings settings, ref Dictionary> dependencyMap, AddressablesContentState cacheData) { - var cacheData = LoadContentState(cacheDataPath); - if (cacheData == null) - { - dependencyMap = null; - return; - } - List noBundledAssetGroupSchema = new List(); List noStaticContent = new List(); @@ -622,18 +615,71 @@ internal static void GatherExplicitModifiedEntries(AddressableAssetSettings sett /// A dictionary mapping explicit changed entries to their dependencies. public static Dictionary> GatherModifiedEntriesWithDependencies(AddressableAssetSettings settings, string cachePath) { - Dictionary> modifiedData = new Dictionary>(); - GatherExplicitModifiedEntries(settings, cachePath, ref modifiedData); - GetStaticContentDependenciesForEntries(settings, ref modifiedData); + var modifiedData = new Dictionary>(); + AddressablesContentState cacheData = LoadContentState(cachePath); + if (cacheData == null) + return modifiedData; + + GatherExplicitModifiedEntries(settings, ref modifiedData, cacheData); + GetStaticContentDependenciesForEntries(settings, ref modifiedData, cacheData); return modifiedData; } - internal static void GetStaticContentDependenciesForEntries(AddressableAssetSettings settings, ref Dictionary> dependencyMap) + internal static Dictionary GetGroupGuidToCacheBundleNameMap(AddressablesContentState cacheData) + { + var bundleIdToCacheInfo = new Dictionary(); + foreach (CachedBundleState bundleInfo in cacheData.cachedBundles) + { + if (bundleInfo != null && bundleInfo.data is AssetBundleRequestOptions options) + bundleIdToCacheInfo[bundleInfo.bundleFileId] = options.BundleName; + } + + var groupGuidToCacheBundleName = new Dictionary(); + foreach (CachedAssetState cacheInfo in cacheData.cachedInfos) + { + if(cacheInfo != null && bundleIdToCacheInfo.TryGetValue(cacheInfo.bundleFileId, out string bundleName)) + groupGuidToCacheBundleName[cacheInfo.groupGuid] = bundleName; + } + return groupGuidToCacheBundleName; + } + + internal static HashSet GetGroupGuidsWithUnchangedBundleName(AddressableAssetSettings settings, Dictionary> dependencyMap, AddressablesContentState cacheData) + { + var result = new HashSet(); + if (cacheData == null) + return result; + + Dictionary groupGuidToCacheBundleName = GetGroupGuidToCacheBundleNameMap(cacheData); + + foreach (AddressableAssetGroup group in settings.groups) + { + if (group == null || !group.HasSchema()) + continue; + + var schema = group.GetSchema(); + List bundleInputDefinitions = new List(); + + 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; + } + + internal static void GetStaticContentDependenciesForEntries(AddressableAssetSettings settings, ref Dictionary> dependencyMap, AddressablesContentState cacheData = null) { Dictionary groupHasStaticContentMap = new Dictionary(); if (dependencyMap == null) return; + + HashSet groupGuidsWithUnchangedBundleName = GetGroupGuidsWithUnchangedBundleName(settings, dependencyMap, cacheData); foreach (AddressableAssetEntry entry in dependencyMap.Keys) { @@ -647,22 +693,25 @@ internal static void GetStaticContentDependenciesForEntries(AddressableAssetSett { string guid = AssetDatabase.AssetPathToGUID(dependency); var depEntry = settings.FindAssetEntry(guid); - if (depEntry != null) + if (depEntry == null) + continue; + + if (!groupHasStaticContentMap.TryGetValue(depEntry.parentGroup, out bool groupHasStaticContentEnabled)) + { + groupHasStaticContentEnabled = depEntry.parentGroup.HasSchema() && + depEntry.parentGroup.GetSchema().StaticContent; + + if (groupGuidsWithUnchangedBundleName.Contains(depEntry.parentGroup.Guid)) + continue; + + groupHasStaticContentMap.Add(depEntry.parentGroup, groupHasStaticContentEnabled); + } + + if (!dependencyMap.ContainsKey(depEntry) && groupHasStaticContentEnabled) { - if (!groupHasStaticContentMap.TryGetValue(depEntry.parentGroup, out bool groupHasStaticContentEnabled)) - { - groupHasStaticContentEnabled = depEntry.parentGroup.HasSchema() && - depEntry.parentGroup.GetSchema().StaticContent; - - groupHasStaticContentMap.Add(depEntry.parentGroup, groupHasStaticContentEnabled); - } - - if (!dependencyMap.ContainsKey(depEntry) && groupHasStaticContentEnabled) - { - if (!dependencyMap.ContainsKey(entry)) - dependencyMap.Add(entry, new List()); - dependencyMap[entry].Add(depEntry); - } + if (!dependencyMap.ContainsKey(entry)) + dependencyMap.Add(entry, new List()); + dependencyMap[entry].Add(depEntry); } } } diff --git a/Editor/Build/DataBuilders/BuildScriptBase.cs b/Editor/Build/DataBuilders/BuildScriptBase.cs index 4f2c83b0..7db59578 100644 --- a/Editor/Build/DataBuilders/BuildScriptBase.cs +++ b/Editor/Build/DataBuilders/BuildScriptBase.cs @@ -92,7 +92,7 @@ public TResult BuildData(AddressablesDataBuilderInput builderInput) whe } if (builderInput.Logger == null && m_Log != null) - WriteBuildLog((BuildLog)m_Log, Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables"); + WriteBuildLog((BuildLog)m_Log, Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath); return result; } diff --git a/Editor/Build/DataBuilders/BuildScriptPackedMode.cs b/Editor/Build/DataBuilders/BuildScriptPackedMode.cs index f3c663b5..f83a8ff5 100644 --- a/Editor/Build/DataBuilders/BuildScriptPackedMode.cs +++ b/Editor/Build/DataBuilders/BuildScriptPackedMode.cs @@ -167,7 +167,7 @@ protected virtual TResult DoBuild(AddressablesDataBuilderInput builderI { ExtractDataTask extractData = new ExtractDataTask(); List carryOverCachedState = new List(); - var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; var playerBuildVersion = builderInput.PlayerVersion; if (m_AllBundleInputDefs.Count > 0) @@ -624,17 +624,17 @@ protected virtual string ProcessBundledAssetSchema( } var bundleInputDefs = new List(); - List list = PrepGroupBundlePacking(assetGroup, bundleInputDefs, schema); + var list = PrepGroupBundlePacking(assetGroup, bundleInputDefs, schema); aaContext.assetEntries.AddRange(list); - HandleDuplicateBundleNames(bundleInputDefs, aaContext.bundleToAssetGroup, assetGroup.Guid, out var uniqueNames); + List uniqueNames = HandleDuplicateBundleNames(bundleInputDefs, aaContext.bundleToAssetGroup, assetGroup.Guid); m_OutputAssetBundleNames.AddRange(uniqueNames); m_AllBundleInputDefs.AddRange(bundleInputDefs); return string.Empty; } - internal void HandleDuplicateBundleNames(List bundleInputDefs, Dictionary bundleToAssetGroup, string assetGroupGuid, out List generatedUniqueNames) + internal static List HandleDuplicateBundleNames(List bundleInputDefs, Dictionary bundleToAssetGroup = null, string assetGroupGuid = null) { - generatedUniqueNames = new List(); + var generatedUniqueNames = new List(); var handledNames = new HashSet(); for (int i = 0; i < bundleInputDefs.Count; i++) @@ -657,8 +657,10 @@ internal void HandleDuplicateBundleNames(List bundleInputDefs, bundleBuild.assetBundleName = hashedAssetBundleName; bundleInputDefs[i] = bundleBuild; - bundleToAssetGroup.Add(hashedAssetBundleName, assetGroupGuid); + if(bundleToAssetGroup != null) + bundleToAssetGroup.Add(hashedAssetBundleName, assetGroupGuid); } + return generatedUniqueNames; } internal static string ErrorCheckBundleSettings(BundledAssetGroupSchema schema, AddressableAssetGroup assetGroup, AddressableAssetSettings settings) @@ -703,18 +705,22 @@ internal static string CalculateGroupHash(BundledAssetGroupSchema.BundleInternal throw new Exception("Invalid naming mode."); } - internal static List PrepGroupBundlePacking(AddressableAssetGroup assetGroup, List bundleInputDefs, BundledAssetGroupSchema schema) + internal static List PrepGroupBundlePacking(AddressableAssetGroup assetGroup, List bundleInputDefs, BundledAssetGroupSchema schema, Func entryFilter = null) { + var combinedEntries = new List(); var packingMode = schema.BundleMode; var namingMode = schema.InternalBundleIdMode; - var combinedEntries = new List(); switch (packingMode) { case BundledAssetGroupSchema.BundlePackingMode.PackTogether: { var allEntries = new List(); foreach (AddressableAssetEntry a in assetGroup.entries) - a.GatherAllAssets(allEntries, true, true, false); + { + if (entryFilter != null && !entryFilter(a)) + continue; + a.GatherAllAssets(allEntries, true, true, false, entryFilter); + } combinedEntries.AddRange(allEntries); GenerateBuildInputDefinitions(allEntries, bundleInputDefs, CalculateGroupHash(namingMode, assetGroup, allEntries), "all"); } break; @@ -722,8 +728,10 @@ internal static List PrepGroupBundlePacking(AddressableAs { foreach (AddressableAssetEntry a in assetGroup.entries) { + if (entryFilter != null && !entryFilter(a)) + continue; var allEntries = new List(); - a.GatherAllAssets(allEntries, true, true, false); + a.GatherAllAssets(allEntries, true, true, false, entryFilter); combinedEntries.AddRange(allEntries); GenerateBuildInputDefinitions(allEntries, bundleInputDefs, CalculateGroupHash(namingMode, assetGroup, allEntries), a.address); } @@ -733,6 +741,8 @@ internal static List PrepGroupBundlePacking(AddressableAs var labelTable = new Dictionary>(); foreach (AddressableAssetEntry a in assetGroup.entries) { + if (entryFilter != null && !entryFilter(a)) + continue; var sb = new StringBuilder(); foreach (var l in a.labels) sb.Append(l); @@ -747,7 +757,11 @@ internal static List PrepGroupBundlePacking(AddressableAs { var allEntries = new List(); foreach (var a in entryGroup.Value) - a.GatherAllAssets(allEntries, true, true, false); + { + if (entryFilter != null && !entryFilter(a)) + continue; + a.GatherAllAssets(allEntries, true, true, false, entryFilter); + } combinedEntries.AddRange(allEntries); GenerateBuildInputDefinitions(allEntries, bundleInputDefs, CalculateGroupHash(namingMode, assetGroup, allEntries), entryGroup.Key); } @@ -974,6 +988,10 @@ internal void AddPostCatalogUpdatesInternal(AddressableAssetGroup assetGroup, Li { if (File.Exists(bundleNameWithoutHash)) File.Delete(bundleNameWithoutHash); + string destFolder = Path.GetDirectoryName(bundleNameWithoutHash); + if (!string.IsNullOrEmpty(destFolder) && !Directory.Exists(destFolder)) + Directory.CreateDirectory(destFolder); + File.Move(targetBundlePath, bundleNameWithoutHash); } diff --git a/Editor/Build/DataBuilders/BuildScriptVirtualMode.cs b/Editor/Build/DataBuilders/BuildScriptVirtualMode.cs index 9efabe55..3d53b936 100644 --- a/Editor/Build/DataBuilders/BuildScriptVirtualMode.cs +++ b/Editor/Build/DataBuilders/BuildScriptVirtualMode.cs @@ -65,7 +65,7 @@ private string m_PathFormat get { if (string.IsNullOrEmpty(m_pathFormatStore)) - m_pathFormatStore = "{0}Library/com.unity.addressables/{1}_BuildScriptVirtualMode.json"; + m_pathFormatStore = "{0}" + Addressables.LibraryPath + "{1}_BuildScriptVirtualMode.json"; return m_pathFormatStore; } set { m_pathFormatStore = value; } diff --git a/Editor/Build/FastModeInitializationOperation.cs b/Editor/Build/FastModeInitializationOperation.cs index 83b7300b..8eb79729 100644 --- a/Editor/Build/FastModeInitializationOperation.cs +++ b/Editor/Build/FastModeInitializationOperation.cs @@ -5,6 +5,7 @@ using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.AddressableAssets.ResourceProviders; +using UnityEngine.AddressableAssets.Utility; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; @@ -16,12 +17,14 @@ internal class FastModeInitializationOperation : AsyncOperationBase> groupOp; public FastModeInitializationOperation(AddressablesImpl addressables, AddressableAssetSettings settings) { m_addressables = addressables; m_settings = settings; m_addressables.ResourceManager.RegisterForCallbacks(); + m_Diagnostics = new ResourceManagerDiagnostics(m_addressables.ResourceManager); } static T GetBuilderOfType(AddressableAssetSettings settings) where T : class, IDataBuilder @@ -52,6 +55,13 @@ protected override void Execute() var locator = new AddressableAssetSettingsLocator(m_settings); m_addressables.AddResourceLocator(locator); m_addressables.AddResourceLocator(new DynamicResourceLocator(m_addressables)); + m_addressables.ResourceManager.postProfilerEvents = ProjectConfigData.PostProfilerEvents; + if (!m_addressables.ResourceManager.postProfilerEvents) + { + m_Diagnostics.Dispose(); + m_Diagnostics = null; + m_addressables.ResourceManager.ClearDiagnosticCallbacks(); + } //NOTE: for some reason, the data builders can get lost from the settings asset during a domain reload - this only happens in tests and custom instance and scene providers are not needed m_addressables.InstanceProvider = db == null ? new InstanceProvider() : ObjectInitializationData.CreateSerializedInitializationData(db.instanceProviderType.Value).CreateInstance(); m_addressables.SceneProvider = db == null ? new SceneProvider() : ObjectInitializationData.CreateSerializedInitializationData(db.sceneProviderType.Value).CreateInstance(); diff --git a/Editor/Build/RevertUnchangedAssetsToPreviousAssetState.cs b/Editor/Build/RevertUnchangedAssetsToPreviousAssetState.cs index 93f5ffa1..5430fb75 100644 --- a/Editor/Build/RevertUnchangedAssetsToPreviousAssetState.cs +++ b/Editor/Build/RevertUnchangedAssetsToPreviousAssetState.cs @@ -130,6 +130,16 @@ internal static List DetermineRequiredAssetEntryUpdat } return operations; } + + private static bool IsPreviouslyRevertedDependency(string bundleFileId, ContentUpdateContext contentUpdateContext) + { + foreach(CachedAssetState state in contentUpdateContext.PreviousAssetStateCarryOver) + { + if (state.bundleFileId == bundleFileId) + return true; + } + return false; + } internal static void ApplyAssetEntryUpdates( List operations, @@ -141,9 +151,8 @@ internal static void ApplyAssetEntryUpdates( { //Check that we can replace the entry in the file registry //before continuing. Past this point destructive actions are taken. - if (contentUpdateContext.Registry.ReplaceBundleEntry( - Path.GetFileNameWithoutExtension(operation.PreviousBuildPath), - operation.PreviousAssetState.bundleFileId)) + if (contentUpdateContext.Registry.ReplaceBundleEntry(Path.GetFileNameWithoutExtension(operation.PreviousBuildPath), operation.PreviousAssetState.bundleFileId) || + IsPreviouslyRevertedDependency(operation.PreviousAssetState.bundleFileId, contentUpdateContext)) { File.Delete(operation.CurrentBuildPath); operation.BundleCatalogEntry.InternalId = operation.PreviousAssetState.bundleFileId; diff --git a/Editor/Build/SceneManagerState.cs b/Editor/Build/SceneManagerState.cs index 858c1982..98c5a678 100644 --- a/Editor/Build/SceneManagerState.cs +++ b/Editor/Build/SceneManagerState.cs @@ -4,6 +4,7 @@ using UnityEditor.AddressableAssets.Settings; using UnityEditor.SceneManagement; using UnityEngine; +using UnityEngine.AddressableAssets; using UnityEngine.Serialization; namespace UnityEditor.AddressableAssets.Build @@ -94,13 +95,16 @@ EditorBuildSettingsScene[] GetEditorBuildSettingScenes() return scenes.ToArray(); } - const string k_DefaultPath = "Library/com.unity.addressables/SceneManagerState.json"; + static string s_DefaultPath = Addressables.LibraryPath + "SceneManagerState.json"; /// /// Record the state of the EditorSceneManager and save to a JSON file. /// /// The path to save the recorded state. - public static void Record(string path = k_DefaultPath) + public static void Record(string path = "") { + if (string.IsNullOrEmpty(path)) + path = s_DefaultPath; + try { var dir = Path.GetDirectoryName(path); @@ -134,8 +138,12 @@ public static void AddScenesForPlayMode(List playModeS /// /// The path to load the state data from. This file is generated by calling SceneManagerState.Record. /// If true, the recorded active scenes are restored. EditorBuildSettings.scenes are always restored. - public static void Restore(string path = k_DefaultPath, bool restoreSceneManagerSetup = false) + public static void Restore(string path = "", bool restoreSceneManagerSetup = false) { + + if (string.IsNullOrEmpty(path)) + path = s_DefaultPath; + try { var state = JsonUtility.FromJson(File.ReadAllText(path)); diff --git a/Editor/GUI/AssetReferenceDrawer.cs b/Editor/GUI/AssetReferenceDrawer.cs index 83cfdd17..955b47dc 100644 --- a/Editor/GUI/AssetReferenceDrawer.cs +++ b/Editor/GUI/AssetReferenceDrawer.cs @@ -27,6 +27,7 @@ class AssetReferenceDrawer : PropertyDrawer internal string m_AssetName; internal Rect assetDropDownRect; internal const string noAssetString = "None (AddressableAsset)"; + internal const string forceAddressableString = "Make Addressable - "; internal AssetReference m_AssetRefObject; internal GUIContent m_label; internal bool m_ReferencesSame = true; @@ -251,6 +252,11 @@ bool ApplySelectionChanges(SerializedProperty property, AddressableAssetSettings SetObject(property, null, out guid); newGuid = string.Empty; } + else if (newGuid == forceAddressableString) + { + checkToForceAddressable = guid; + newGuid = string.Empty; + } else if (guid != newGuid) { if (SetObject(property, AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(newGuid)), out guid)) @@ -942,6 +948,8 @@ protected override void SelectionChanged(IList selectedIds) if (assetRefItem != null && !string.IsNullOrEmpty(assetRefItem.AssetPath)) { m_Drawer.newGuid = assetRefItem.Guid; + if (string.IsNullOrEmpty(m_Drawer.newGuid)) + m_Drawer.newGuid = assetRefItem.AssetPath; } else { @@ -985,7 +993,7 @@ protected override TreeViewItem BuildRoot() if (!string.IsNullOrEmpty(m_NonAddressedAsset)) { var item = new AssetRefTreeViewItem(m_NonAddressedAsset.GetHashCode(), 0, - "Make Addressable - " + m_NonAddressedAsset, string.Empty); + AssetReferenceDrawer.forceAddressableString + m_NonAddressedAsset, AssetReferenceDrawer.forceAddressableString); item.icon = m_WarningIcon; root.AddChild(item); } diff --git a/Editor/Settings/AddressableAssetEntry.cs b/Editor/Settings/AddressableAssetEntry.cs index 03f3fd3e..1606f541 100644 --- a/Editor/Settings/AddressableAssetEntry.cs +++ b/Editor/Settings/AddressableAssetEntry.cs @@ -851,8 +851,14 @@ internal void CreateCatalogEntriesInternal(List entries if (keyList.Count == 0) return; + //The asset may have previously been invalid. Since then, it may have been re-imported. + //This can occur in particular when using ScriptedImporters with complex, multi-step behavior. + //Double-check the type here in case the asset has been imported correctly after we cached its type. + if (MainAssetType == typeof(DefaultAsset)) + m_cachedMainAssetType = null; + Type mainType = AddressableAssetUtility.MapEditorTypeToRuntimeType(MainAssetType, false); - if (mainType == null && !IsInResources) + if ((mainType == null || mainType == typeof(DefaultAsset)) && !IsInResources) { var t = MainAssetType; Debug.LogWarningFormat("Type {0} is in editor assembly {1}. Asset location with internal id {2} will be stripped.", t.Name, t.Assembly.FullName, assetPath); @@ -870,7 +876,7 @@ internal void CreateCatalogEntriesInternal(List entries foreach (var t in GatherSubObjectTypes(ids, guid)) entries.Add(new ContentCatalogDataEntry(t, assetPath, providerType, keyList, dependencies, extraData)); } - else if (mainType != null) + else if (mainType != null && mainType != typeof(DefaultAsset)) { entries.Add(new ContentCatalogDataEntry(mainType, assetPath, providerType, keyList, dependencies, extraData)); } @@ -887,7 +893,7 @@ static internal IEnumerable GatherSubObjectTypes(ObjectIdentifier[] ids, s if (typeof(Component).IsAssignableFrom(objType)) continue; Type rtType = AddressableAssetUtility.MapEditorTypeToRuntimeType(objType, false); - if (rtType != null && !typesSeen.Contains(rtType)) + if (rtType != null && rtType != typeof(DefaultAsset) && !typesSeen.Contains(rtType)) { yield return rtType; typesSeen.Add(rtType); diff --git a/Editor/Settings/AddressableAssetGroup.cs b/Editor/Settings/AddressableAssetGroup.cs index 2dbc5efa..2066fbcf 100644 --- a/Editor/Settings/AddressableAssetGroup.cs +++ b/Editor/Settings/AddressableAssetGroup.cs @@ -60,15 +60,14 @@ public virtual string Name } set { - string temp = value; - temp = temp.Replace('/', '-'); - temp = temp.Replace('\\', '-'); - if (temp != value) + string newName = value; + newName = newName.Replace('/', '-'); + newName = newName.Replace('\\', '-'); + if (newName != value) Debug.Log("Group names cannot include '\\' or '/'. Replacing with '-'. " + m_GroupName); - if (m_GroupName != temp) + if (m_GroupName != newName) { string previousName = m_GroupName; - m_GroupName = temp; string guid; long localId; @@ -79,27 +78,31 @@ public virtual string Name { var folder = Path.GetDirectoryName(path); var extension = Path.GetExtension(path); - var newPath = $"{folder}/{m_GroupName}{extension}".Replace('\\', '/'); + var newPath = $"{folder}/{newName}{extension}".Replace('\\', '/'); if (path != newPath) { var setPath = AssetDatabase.MoveAsset(path, newPath); - if (!string.IsNullOrEmpty(setPath) || !RenameSchemaAssets()) + bool success = false; + if (string.IsNullOrEmpty(setPath)) + { + name = m_GroupName = newName; + success = RenameSchemaAssets(); + } + + if (success == false) { //unable to rename group due to invalid file name Debug.LogError("Rename of Group failed. " + setPath); - m_GroupName = previousName; + name = m_GroupName = previousName; } } } - - name = m_GroupName; } else { //this isn't a valid asset, which means it wasn't persisted, so just set the object name to the desired display name. - name = m_GroupName; + name = m_GroupName = newName; } - SetDirty(AddressableAssetSettings.ModificationEvent.GroupRenamed, this, true, true); } @@ -145,7 +148,12 @@ public AddressableAssetGroupSchema AddSchema(AddressableAssetGroupSchema schema, if (added != null) { added.Group = this; + if (m_Settings && m_Settings.IsPersisted) + EditorUtility.SetDirty(added); + SetDirty(AddressableAssetSettings.ModificationEvent.GroupSchemaAdded, this, postEvent, true); + + AssetDatabase.SaveAssets(); } return added; } @@ -162,7 +170,12 @@ public AddressableAssetGroupSchema AddSchema(Type type, bool postEvent = true) if (added != null) { added.Group = this; + if (m_Settings && m_Settings.IsPersisted) + EditorUtility.SetDirty(added); + SetDirty(AddressableAssetSettings.ModificationEvent.GroupSchemaAdded, this, postEvent, true); + + AssetDatabase.SaveAssets(); } return added; } diff --git a/Editor/Settings/AddressableAssetSettings.cs b/Editor/Settings/AddressableAssetSettings.cs index f3a9f661..03e41598 100644 --- a/Editor/Settings/AddressableAssetSettings.cs +++ b/Editor/Settings/AddressableAssetSettings.cs @@ -472,7 +472,7 @@ internal string GetContentStateBuildPath() string p = ConfigFolder; if (!string.IsNullOrEmpty(m_ContentStateBuildPath)) p = m_ContentStateBuildPath; - p = Path.Combine(p, PlatformMappingService.GetPlatform().ToString()); + p = Path.Combine(p, PlatformMappingService.GetPlatformPathSubFolder()); return p; } diff --git a/Editor/Settings/AddressableAssetUtility.cs b/Editor/Settings/AddressableAssetUtility.cs index 8bad3640..d6e2efab 100644 --- a/Editor/Settings/AddressableAssetUtility.cs +++ b/Editor/Settings/AddressableAssetUtility.cs @@ -91,7 +91,7 @@ internal static Type MapEditorTypeToRuntimeType(Type t, bool allowFolders) return t; } - if (t == typeof(DefaultAsset) && allowFolders) + if (t == typeof(DefaultAsset)) return typeof(DefaultAsset); //try to remap the editor type to a runtime type @@ -286,24 +286,34 @@ internal static bool IsUsingVCIntegration() return Provider.isActive && Provider.enabled; } - private static bool DisplayDialogueForEditingLockedFile(string path) + internal static bool IsVCAssetOpenForEdit(string path) { - return EditorUtility.DisplayDialog("Attemping to edit locked file", - "File " + path + " is locked. Check out?", "Yes", "No"); + AssetList VCAssets = GetVCAssets(path); + foreach(Asset vcAsset in VCAssets) + { + if (vcAsset.path == path) + return Provider.IsOpenForEdit(vcAsset); + } + return false; } - private static bool MakeAssetEditable(Object target, string path) + internal static AssetList GetVCAssets(string path) + { + Task op = Provider.Status(path); + op.Wait(); + return op.assetList; + } + + private static bool MakeAssetEditable(Asset asset) { - if (string.IsNullOrEmpty(path)) - return false; #if UNITY_2019_4_OR_NEWER - if(!AssetDatabase.IsOpenForEdit(target)) - return AssetDatabase.MakeEditable(path); + if(!AssetDatabase.IsOpenForEdit(asset.path)) + return AssetDatabase.MakeEditable(asset.path); #else - Asset asset = Provider.GetAssetByPath(path); - if (asset != null && !Provider.IsOpenForEdit(asset)) + if(!Provider.IsOpenForEdit(asset)) { - Task task = Provider.Checkout(asset, CheckoutMode.Asset); + CheckoutMode mode = asset.isMeta ? CheckoutMode.Meta : CheckoutMode.Asset; + Task task = Provider.Checkout(asset, mode); task.Wait(); return task.success; } @@ -311,20 +321,42 @@ private static bool MakeAssetEditable(Object target, string path) return false; } - internal static bool OpenAssetIfUsingVCIntegration(Object target, bool exitGUI = false) - { - if (target == null) + internal static bool OpenAssetIfUsingVCIntegration(Object target, bool exitGUI = false) + { + if (!IsUsingVCIntegration() || target == null) return false; - return OpenAssetIfUsingVCIntegration(target, AssetDatabase.GetAssetOrScenePath(target), exitGUI); - } + return OpenAssetIfUsingVCIntegration(AssetDatabase.GetAssetOrScenePath(target), exitGUI); + } - internal static bool OpenAssetIfUsingVCIntegration(Object target, string path, bool exitGUI = false) + internal static bool OpenAssetIfUsingVCIntegration(string path, bool exitGUI = false) { - bool openedAsset = false; - if (string.IsNullOrEmpty(path) || !IsUsingVCIntegration()) - return openedAsset; - if (DisplayDialogueForEditingLockedFile(path)) - openedAsset = MakeAssetEditable(target, path); + if (!IsUsingVCIntegration() || string.IsNullOrEmpty(path)) + return false; + + AssetList assets = GetVCAssets(path); + var uneditableAssets = new List(); + string message = "Check out file(s)?\n\n"; + foreach(Asset asset in assets) + { + if(!Provider.IsOpenForEdit(asset)) + { + uneditableAssets.Add(asset); + message += $"{asset.path}\n"; + } + } + + if (uneditableAssets.Count == 0) + return false; + + bool openedAsset = true; + if (EditorUtility.DisplayDialog("Attempting to modify files that are uneditable", message, "Yes", "No")) + { + foreach(Asset asset in uneditableAssets) + { + if (!MakeAssetEditable(asset)) + openedAsset = false; + } + } if (exitGUI) GUIUtility.ExitGUI(); return openedAsset; diff --git a/Editor/Settings/Preferences.cs b/Editor/Settings/Preferences.cs index 212c8d43..17066cc4 100644 --- a/Editor/Settings/Preferences.cs +++ b/Editor/Settings/Preferences.cs @@ -37,7 +37,7 @@ protected override void CloseScope() internal class Properties { public static readonly GUIContent buildSettings = EditorGUIUtility.TrTextContent("Build Settings"); - public static readonly GUIContent buildLayoutReport = EditorGUIUtility.TrTextContent("Debug Build Layout", $"A debug build layout file will be generated as part of the build process. The file will put written to {BuildLayoutGenerationTask.kLayoutTextFile}"); + public static readonly GUIContent buildLayoutReport = EditorGUIUtility.TrTextContent("Debug Build Layout", $"A debug build layout file will be generated as part of the build process. The file will put written to {BuildLayoutGenerationTask.m_LayoutTextFile}"); } static AddressablesPreferences() diff --git a/Runtime/Addressables.cs b/Runtime/Addressables.cs index 8a7bb22c..fe5d205e 100644 --- a/Runtime/Addressables.cs +++ b/Runtime/Addressables.cs @@ -192,6 +192,9 @@ public enum MergeMode /// public static string StreamingAssetsSubFolder { get { return m_Addressables.StreamingAssetsSubFolder; } } + + internal static string LibraryPath = "Library/com.unity.addressables/"; + /// /// The path used by the Addressables system for its initialization data. /// diff --git a/Runtime/AddressablesImpl.cs b/Runtime/AddressablesImpl.cs index 0aa02112..737ebbe3 100644 --- a/Runtime/AddressablesImpl.cs +++ b/Runtime/AddressablesImpl.cs @@ -184,7 +184,7 @@ public string StreamingAssetsSubFolder public string BuildPath { - get { return "Library/com.unity.addressables/" + StreamingAssetsSubFolder + "/" + PlatformMappingService.GetPlatform(); } + get { return Addressables.LibraryPath + StreamingAssetsSubFolder + "/" + PlatformMappingService.GetPlatformPathSubFolder(); } } public string PlayerBuildDataPath @@ -722,6 +722,20 @@ public void Release(TObject obj) public void Release(AsyncOperationHandle handle) { + if (typeof(TObject) == typeof(SceneInstance)) + { + SceneInstance sceneInstance = (SceneInstance)Convert.ChangeType(handle.Result,typeof(SceneInstance)); + if (sceneInstance.Scene.isLoaded && handle.ReferenceCount == 1) + { + if (SceneOperationCount == 1 && m_SceneInstances.First().Equals(handle)) + m_SceneInstances.Clear(); + UnloadSceneAsync(handle); + } + else if(!sceneInstance.Scene.isLoaded && handle.ReferenceCount == 2) + { + handle.Completed += s => Release(handle); + } + } m_ResourceManager.Release(handle); } diff --git a/Runtime/Initialization/AddressablesRuntimeProperties.cs b/Runtime/Initialization/AddressablesRuntimeProperties.cs index a74adb20..283a6dc6 100644 --- a/Runtime/Initialization/AddressablesRuntimeProperties.cs +++ b/Runtime/Initialization/AddressablesRuntimeProperties.cs @@ -9,6 +9,10 @@ namespace UnityEngine.AddressableAssets.Initialization /// public static class AddressablesRuntimeProperties { + // cache these to avoid GC allocations + static Stack s_TokenStack = new Stack(32); + static Stack s_TokenStartStack = new Stack(32); + #if !UNITY_EDITOR && UNITY_WSA_10_0 && ENABLE_DOTNET static Assembly[] GetAssemblies() { @@ -130,19 +134,62 @@ public static string EvaluateString(string inputString, char startDelimiter, cha { if (string.IsNullOrEmpty(inputString)) return string.Empty; + + s_TokenStack.Push(inputString); + int popTokenAt = inputString.Length; + char[] delimiters = {startDelimiter, endDelimiter}; + bool delimitersMatch = startDelimiter == endDelimiter; - while (true) + int i = inputString.IndexOf(startDelimiter); + while (i >= 0) { - int i = inputString.IndexOf(startDelimiter); - if (i < 0) - return inputString; - int e = inputString.IndexOf(endDelimiter, i + 1); - if (e < i) - return inputString; - var token = inputString.Substring(i + 1, e - i - 1); - var tokenVal = varFunc == null ? string.Empty : varFunc(token); - inputString = inputString.Substring(0, i) + tokenVal + inputString.Substring(e + 1); + char c = inputString[i]; + if (c == startDelimiter && (!delimitersMatch || s_TokenStartStack.Count == 0)) + { + s_TokenStartStack.Push(i); + i++; + } + else if (c == endDelimiter && s_TokenStartStack.Count > 0) + { + int start = s_TokenStartStack.Peek(); + string token = inputString.Substring(start + 1, i - start - 1); + string tokenVal; + + if (popTokenAt <= i) + { + s_TokenStack.Pop(); + } + + // check if the token is already included + if (s_TokenStack.Contains(token)) + tokenVal = "#ERROR-CyclicToken#"; + else + { + tokenVal = varFunc == null ? string.Empty : varFunc(token); + s_TokenStack.Push(token); + } + + i = s_TokenStartStack.Pop(); + popTokenAt = i + tokenVal.Length + 1; + + if (i > 0) + { + int rhsStartIndex = i + token.Length + 2; + if (rhsStartIndex == inputString.Length) + inputString = inputString.Substring(0, i) + tokenVal; + else + inputString = inputString.Substring(0, i) + tokenVal + inputString.Substring(rhsStartIndex); + } + else + inputString = tokenVal + inputString.Substring(i + token.Length + 2); + } + + i = inputString.IndexOfAny(delimiters, i); } + + s_TokenStack.Clear(); + s_TokenStartStack.Clear(); + return inputString; } } } diff --git a/Runtime/ResourceLocators/DynamicResourceLocators.cs b/Runtime/ResourceLocators/DynamicResourceLocators.cs index 5bf35155..4f79e691 100644 --- a/Runtime/ResourceLocators/DynamicResourceLocators.cs +++ b/Runtime/ResourceLocators/DynamicResourceLocators.cs @@ -16,6 +16,30 @@ internal class DynamicResourceLocator : IResourceLocator AddressablesImpl m_Addressables; public string LocatorId => nameof(DynamicResourceLocator); public virtual IEnumerable Keys => new object[0]; + private string m_AtlasSpriteProviderId = null; + + private string AtlasSpriteProviderId + { + get + { + if (!string.IsNullOrEmpty(m_AtlasSpriteProviderId)) + return m_AtlasSpriteProviderId; + + var providers = m_Addressables.ResourceManager.ResourceProviders; + foreach (IResourceProvider provider in providers) + { + if (provider is AtlasSpriteProvider) + { + m_AtlasSpriteProviderId = provider.ProviderId; + return m_AtlasSpriteProviderId; + } + } + + // if nothing found, fallback to the default name + return typeof(AtlasSpriteProvider).FullName; + } + } + public DynamicResourceLocator(AddressablesImpl addr) { @@ -32,7 +56,7 @@ public bool Locate(object key, Type type, out IList locations if (type == typeof(Sprite)) m_Addressables.GetResourceLocations(mainKey, typeof(SpriteAtlas), out locs); } - + if (locs != null && locs.Count > 0) { locations = new List(locs.Count); @@ -48,7 +72,7 @@ internal void CreateDynamicLocations(Type type, IList locatio { if (type == typeof(Sprite) && mainLoc.ResourceType == typeof(U2D.SpriteAtlas)) { - locations.Add(new ResourceLocationBase(locName, $"{mainLoc.InternalId}[{subKey}]", typeof(AtlasSpriteProvider).FullName, type, new IResourceLocation[] { mainLoc })); + locations.Add(new ResourceLocationBase(locName, $"{mainLoc.InternalId}[{subKey}]", AtlasSpriteProviderId, type, new IResourceLocation[] { mainLoc })); } else { diff --git a/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs b/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs index bd1e5fe0..baae882b 100644 --- a/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs +++ b/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs @@ -216,19 +216,40 @@ internal System.Threading.Tasks.Task Task { return System.Threading.Tasks.Task.FromResult(default(TObject)); } + System.Threading.WaitHandle waitHandle = null; if (Status == AsyncOperationStatus.Succeeded) { + if (Handle.IsValid() && CompletedEventHasListeners) + { + waitHandle = WaitHandle; + Task t = System.Threading.Tasks.Task.Factory.StartNew((Func) (o => + { + var asyncOperation = o as AsyncOperationBase; + if (asyncOperation == null) + return default(TObject); + asyncOperation.IncrementReferenceCount(); + waitHandle.WaitOne(); + var result = (TObject)asyncOperation.Result; + asyncOperation.DecrementReferenceCount(); + return result; + }), this); + return t; + } + return System.Threading.Tasks.Task.FromResult(Result); } - var handle = WaitHandle; + waitHandle = WaitHandle; return System.Threading.Tasks.Task.Factory.StartNew((Func)(o => { var asyncOperation = o as AsyncOperationBase; if (asyncOperation == null) return default(TObject); - handle.WaitOne(); - return (TObject)asyncOperation.Result; + asyncOperation.IncrementReferenceCount(); + waitHandle.WaitOne(); + var result = (TObject)asyncOperation.Result; + asyncOperation.DecrementReferenceCount(); + return result; }), this); #endif } @@ -246,18 +267,39 @@ System.Threading.Tasks.Task IAsyncOperation.Task { return System.Threading.Tasks.Task.FromResult(null); } + System.Threading.WaitHandle waitHandle = null; if (Status == AsyncOperationStatus.Succeeded) { + if (Handle.IsValid() && CompletedEventHasListeners) + { + waitHandle = WaitHandle; + Task t = System.Threading.Tasks.Task.Factory.StartNew((Func)(o => + { + var asyncOperation = o as AsyncOperationBase; + if (asyncOperation == null) + return default(object); + asyncOperation.IncrementReferenceCount(); + waitHandle.WaitOne(); + var result = (object)asyncOperation.Result; + asyncOperation.DecrementReferenceCount(); + return result; + }), this); + return t; + } return System.Threading.Tasks.Task.FromResult(Result); } - var handle = WaitHandle; + + waitHandle = WaitHandle; return System.Threading.Tasks.Task.Factory.StartNew((Func)(o => { var asyncOperation = o as AsyncOperationBase; if (asyncOperation == null) return default(object); - handle.WaitOne(); - return (object)asyncOperation.Result; + asyncOperation.IncrementReferenceCount(); + waitHandle.WaitOne(); + var result = (object)asyncOperation.Result; + asyncOperation.DecrementReferenceCount(); + return result; }), this); #endif } diff --git a/Runtime/ResourceManager/AsyncOperations/ChainOperation.cs b/Runtime/ResourceManager/AsyncOperations/ChainOperation.cs index ec0ceb22..2888b886 100644 --- a/Runtime/ResourceManager/AsyncOperations/ChainOperation.cs +++ b/Runtime/ResourceManager/AsyncOperations/ChainOperation.cs @@ -9,6 +9,8 @@ class ChainOperation : AsyncOperationBase { AsyncOperationHandle m_DepOp; AsyncOperationHandle m_WrappedOp; + DownloadStatus m_depStatus = default; + DownloadStatus m_wrapStatus = default; Func, AsyncOperationHandle> m_Callback; Action> m_CachedOnWrappedCompleted; bool m_ReleaseDependenciesOnFailure = true; @@ -21,7 +23,7 @@ public ChainOperation() protected override void GetDependencies(List deps) { - if(m_DepOp.IsValid()) + if (m_DepOp.IsValid()) deps.Add(m_DepOp); } @@ -31,6 +33,7 @@ public void Init(AsyncOperationHandle dependentOp, Func visited) { - var depStatus = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : default; - var wrapStatus = m_WrappedOp.IsValid() ? m_WrappedOp.InternalGetDownloadStatus(visited) : default; - return new DownloadStatus() { DownloadedBytes = depStatus.DownloadedBytes + wrapStatus.DownloadedBytes, TotalBytes = depStatus.TotalBytes + wrapStatus.TotalBytes, IsDone = IsDone }; + RefreshDownloadStatus(visited); + return new DownloadStatus() { DownloadedBytes = m_depStatus.DownloadedBytes + m_wrapStatus.DownloadedBytes, TotalBytes = m_depStatus.TotalBytes + m_wrapStatus.TotalBytes, IsDone = IsDone }; + } + + void RefreshDownloadStatus(HashSet visited = default) + { + m_depStatus = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : m_depStatus; + m_wrapStatus = m_WrappedOp.IsValid() ? m_WrappedOp.InternalGetDownloadStatus(visited) : m_wrapStatus; } protected override float Progress @@ -122,6 +130,8 @@ class ChainOperationTypelessDepedency : AsyncOperationBase { AsyncOperationHandle m_DepOp; AsyncOperationHandle m_WrappedOp; + DownloadStatus m_depStatus = default; + DownloadStatus m_wrapStatus = default; Func> m_Callback; Action> m_CachedOnWrappedCompleted; bool m_ReleaseDependenciesOnFailure = true; @@ -135,7 +145,7 @@ public ChainOperationTypelessDepedency() protected override void GetDependencies(List deps) { - if(m_DepOp.IsValid()) + if (m_DepOp.IsValid()) deps.Add(m_DepOp); } @@ -145,6 +155,7 @@ public void Init(AsyncOperationHandle dependentOp, Func visited) { - var depStatus = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : default; - var wrapStatus = m_WrappedOp.IsValid() ? m_WrappedOp.InternalGetDownloadStatus(visited) : default; - return new DownloadStatus() { DownloadedBytes = depStatus.DownloadedBytes + wrapStatus.DownloadedBytes, TotalBytes = depStatus.TotalBytes + wrapStatus.TotalBytes, IsDone = IsDone }; + RefreshDownloadStatus(visited); + return new DownloadStatus() { DownloadedBytes = m_depStatus.DownloadedBytes + m_wrapStatus.DownloadedBytes, TotalBytes = m_depStatus.TotalBytes + m_wrapStatus.TotalBytes, IsDone = IsDone }; + } + + void RefreshDownloadStatus(HashSet visited = default) + { + m_depStatus = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : m_depStatus; + m_wrapStatus = m_WrappedOp.IsValid() ? m_WrappedOp.InternalGetDownloadStatus(visited) : m_wrapStatus; } protected override float Progress diff --git a/Runtime/ResourceManager/AsyncOperations/ProviderOperation.cs b/Runtime/ResourceManager/AsyncOperations/ProviderOperation.cs index 86807082..a0b95f68 100644 --- a/Runtime/ResourceManager/AsyncOperations/ProviderOperation.cs +++ b/Runtime/ResourceManager/AsyncOperations/ProviderOperation.cs @@ -47,6 +47,7 @@ public void SetDownloadProgressCallback(Func callback) if (m_GetDownloadProgressCallback != null) m_DownloadStatus = m_GetDownloadProgressCallback(); } + public void SetWaitForCompletionCallback(Func callback) { m_WaitForCompletionCallback = callback; @@ -72,10 +73,8 @@ internal override DownloadStatus GetDownloadStatus(HashSet visited) if (m_GetDownloadProgressCallback != null) m_DownloadStatus = m_GetDownloadProgressCallback(); - else if(IsDone) - m_DownloadStatus.DownloadedBytes = m_DownloadStatus.TotalBytes; - if (IsDone) + if (Status == AsyncOperationStatus.Succeeded) m_DownloadStatus.DownloadedBytes = m_DownloadStatus.TotalBytes; return new DownloadStatus() { DownloadedBytes = m_DownloadStatus.DownloadedBytes + depDLS.DownloadedBytes, TotalBytes = m_DownloadStatus.TotalBytes + depDLS.TotalBytes, IsDone = IsDone }; diff --git a/Runtime/ResourceManager/ResourceProviders/AssetBundleProvider.cs b/Runtime/ResourceManager/ResourceProviders/AssetBundleProvider.cs index adf6d9fd..141fea1c 100644 --- a/Runtime/ResourceManager/ResourceProviders/AssetBundleProvider.cs +++ b/Runtime/ResourceManager/ResourceProviders/AssetBundleProvider.cs @@ -119,13 +119,6 @@ public virtual long ComputeSize(IResourceLocation location, ResourceManager reso return 0; return BundleSize; } - else //If we don't have a hash, any cached version will do. - { - List versions = new List(); - Caching.GetCachedVersions(BundleName, versions); - if (versions.Count > 0) - return 0; - } #endif //ENABLE_CACHING return BundleSize; } @@ -141,6 +134,7 @@ class AssetBundleResource : IAssetBundleResource internal AssetBundleRequestOptions m_Options; int m_Retries; long m_BytesToDownload; + long m_DownloadedBytes; bool m_Completed = false; internal UnityWebRequest CreateWebRequest(IResourceLocation loc) @@ -188,11 +182,11 @@ DownloadStatus GetDownloadStatus() var status = new DownloadStatus() { TotalBytes = m_BytesToDownload, IsDone = PercentComplete() >= 1f }; if (m_BytesToDownload > 0) { - if (m_WebRequestQueueOperation != null) - status.DownloadedBytes = (long)(m_WebRequestQueueOperation.m_WebRequest.downloadedBytes); - else if (PercentComplete() >= 1.0f) - status.DownloadedBytes = status.TotalBytes; + if (m_WebRequestQueueOperation != null && string.IsNullOrEmpty(m_WebRequestQueueOperation.m_WebRequest.error)) + m_DownloadedBytes = (long)(m_WebRequestQueueOperation.m_WebRequest.downloadedBytes); } + + status.DownloadedBytes = m_DownloadedBytes; return status; } @@ -243,8 +237,8 @@ private bool WaitForCompletionHandler() return false; //We don't want to wait for request op to complete if it's a LoadFromFileAsync. Only UWR will complete in a tight loop like this. - if(!(m_RequestOperation is AssetBundleCreateRequest)) - while(!m_RequestOperation.isDone){} + if (!(m_RequestOperation is AssetBundleCreateRequest)) + while (!m_RequestOperation.isDone) {} var assetBundle = GetAssetBundle(); if (!m_Completed && assetBundle != null) @@ -258,6 +252,7 @@ private bool WaitForCompletionHandler() private void BeginOperation() { + m_DownloadedBytes = 0; string path = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location); if (File.Exists(path) || (Application.platform == RuntimePlatform.Android && path.StartsWith("jar:"))) { diff --git a/Runtime/ResourceManager/ResourceProviders/SceneProvider.cs b/Runtime/ResourceManager/ResourceProviders/SceneProvider.cs index f8228cfe..f48d86d2 100644 --- a/Runtime/ResourceManager/ResourceProviders/SceneProvider.cs +++ b/Runtime/ResourceManager/ResourceProviders/SceneProvider.cs @@ -178,9 +178,9 @@ protected override void Execute() { var unloadOp = SceneManager.UnloadSceneAsync(m_Instance.Scene); if (unloadOp == null) - UnloadSceneCompleted(null); + UnloadSceneCompletedNoRelease(null); else - unloadOp.completed += UnloadSceneCompleted; + unloadOp.completed += UnloadSceneCompletedNoRelease; } else UnloadSceneCompleted(null); @@ -202,6 +202,11 @@ private void UnloadSceneCompleted(AsyncOperation obj) if (m_sceneLoadHandle.IsValid()) m_sceneLoadHandle.Release(); } + + private void UnloadSceneCompletedNoRelease(AsyncOperation obj) + { + Complete(m_Instance, true, ""); + } protected override float Progress { diff --git a/Runtime/Services/PlatformMappingService.cs b/Runtime/Services/PlatformMappingService.cs index 823c2e21..537e3691 100644 --- a/Runtime/Services/PlatformMappingService.cs +++ b/Runtime/Services/PlatformMappingService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -67,7 +68,7 @@ public enum AddressablesPlatform public class PlatformMappingService { #if UNITY_EDITOR - private static readonly Dictionary s_BuildTargetMapping = + internal static readonly Dictionary s_BuildTargetMapping = new Dictionary() { {BuildTarget.XboxOne, AddressablesPlatform.XboxOne}, @@ -87,7 +88,7 @@ public class PlatformMappingService #endif }; #endif - private static readonly Dictionary s_RuntimeTargetMapping = + internal static readonly Dictionary s_RuntimeTargetMapping = new Dictionary() { {RuntimePlatform.XboxOne, AddressablesPlatform.XboxOne}, @@ -115,6 +116,13 @@ internal static AddressablesPlatform GetAddressablesPlatformInternal(BuildTarget return AddressablesPlatform.Unknown; } + internal static string GetAddressablesPlatformPathInternal(BuildTarget target) + { + if (s_BuildTargetMapping.ContainsKey(target)) + return s_BuildTargetMapping[target].ToString(); + return target.ToString(); + } + #endif internal static AddressablesPlatform GetAddressablesPlatformInternal(RuntimePlatform platform) { @@ -123,16 +131,37 @@ internal static AddressablesPlatform GetAddressablesPlatformInternal(RuntimePlat return AddressablesPlatform.Unknown; } + internal static string GetAddressablesPlatformPathInternal(RuntimePlatform platform) + { + if (s_RuntimeTargetMapping.ContainsKey(platform)) + return s_RuntimeTargetMapping[platform].ToString(); + return platform.ToString(); + } + /// /// Retrieves the Addressables build platform that is being used. /// /// Returns the Addressables build platform that is being used. + [Obsolete("This API doesn't adapt to the addition of new platforms. Use GetPlatformPathSubFolder instead.")] public static AddressablesPlatform GetPlatform() { #if UNITY_EDITOR return GetAddressablesPlatformInternal(EditorUserBuildSettings.activeBuildTarget); #else return GetAddressablesPlatformInternal(Application.platform); +#endif + } + + /// + /// Retrieves the Addressables platform subfolder of the build platform that is being used. + /// + /// Returns the Addressables platform subfolder of the build platform that is being used. + public static string GetPlatformPathSubFolder() + { +#if UNITY_EDITOR + return GetAddressablesPlatformPathInternal(EditorUserBuildSettings.activeBuildTarget); +#else + return GetAddressablesPlatformPathInternal(Application.platform); #endif } } diff --git a/Tests/Editor/AddressableAssetEntryTests.cs b/Tests/Editor/AddressableAssetEntryTests.cs index f1eefb31..67bb4afe 100644 --- a/Tests/Editor/AddressableAssetEntryTests.cs +++ b/Tests/Editor/AddressableAssetEntryTests.cs @@ -89,6 +89,31 @@ public void CreateCatalogEntries_WhenObjectHasMultipleSubObjectWithSameType_Only Assert.AreEqual(2, entries.Count); } + [Test] + public void DefaultTypeAssetEntry_ResetsCachedTypeData() + { + //Setup + var path = GetAssetPath("entry.prefab"); + PrefabUtility.SaveAsPrefabAsset(new GameObject(), path); + string guid = AssetDatabase.AssetPathToGUID(path); + + AddressableAssetEntry entry = new AddressableAssetEntry(guid, "testaddress", Settings.DefaultGroup, false); + entry.m_cachedMainAssetType = typeof(DefaultAsset); + + //Test + entry.CreateCatalogEntriesInternal(new List(), false, "fakeProvider", new List(), null, new Dictionary() + { + { new GUID(guid), new AssetLoadInfo() {includedObjects = new List()} } + }, + new HashSet(), true, false, false, new HashSet()); + + //Assert + Assert.AreEqual(typeof(GameObject), entry.m_cachedMainAssetType); + + //Cleanup + AssetDatabase.DeleteAsset(path); + } + [Test] public void CreateCatalogEntries_OverridesMainTypeIfWrong() { diff --git a/Tests/Editor/AddressableAssetSettingsLocatorTests.cs b/Tests/Editor/AddressableAssetSettingsLocatorTests.cs index d61d1a28..71d9f665 100644 --- a/Tests/Editor/AddressableAssetSettingsLocatorTests.cs +++ b/Tests/Editor/AddressableAssetSettingsLocatorTests.cs @@ -148,6 +148,7 @@ public void WhenLocatorWithMultipeAssets_LocateWithSharedLabelReturnsMultipleLoc AssertLocateResult(new AddressableAssetSettingsLocator(m_Settings), "label", null, GetPath("asset1.asset"), GetPath("asset2.asset")); } +#if !(UNITY_2021_2_OR_NEWER) [Test] public void WhenLocatorWithAssetsThatMatchAssetsInFolderAndResources_LocateAllMatches() { @@ -162,6 +163,7 @@ public void WhenLocatorWithAssetsThatMatchAssetsInFolderAndResources_LocateAllMa GetPath("asset1") ); } +#endif [Test] public void WhenLocatorWithAssetsInMarkedFolder_LocateWithAssetReferenceSucceeds() @@ -239,9 +241,9 @@ public void WhenLocatorWithAssetsInFolder_LocateWithFolderLabelSucceeds() [Test] public void WhenLocatorWithAssetsInFolderWithSimilarNames_LocateWithAssetKeySucceeds() { - var folderGUID = CreateFolder("TestFolder", new string[] { "asset1", "asset", "asset1_more" }); + var folderGUID = CreateFolder("TestFolder", new string[] { "asset1.asset", "asset.asset", "asset1_more.asset" }); m_Settings.CreateOrMoveEntry(folderGUID, m_Settings.DefaultGroup).address = "TF"; - AssertLocateResult(new AddressableAssetSettingsLocator(m_Settings), "TF/asset1", null, GetPath("TestFolder/asset1")); + AssertLocateResult(new AddressableAssetSettingsLocator(m_Settings), "TF/asset1.asset", null, GetPath("TestFolder/asset1.asset")); } [Test] diff --git a/Tests/Editor/AddressableAssetSettingsTests.cs b/Tests/Editor/AddressableAssetSettingsTests.cs index a86a1d9f..8de97e3a 100644 --- a/Tests/Editor/AddressableAssetSettingsTests.cs +++ b/Tests/Editor/AddressableAssetSettingsTests.cs @@ -242,7 +242,7 @@ public void AddressablesCleanAllCachedData_ClearsAllData() public void DeletingAsset_DoesNotDeleteGroupWithSimilarName() { //Setup - const string groupName = "NewAsset"; + const string groupName = "NewAsset.mat"; string assetPath = GetAssetPath(groupName); diff --git a/Tests/Editor/AnalyzeRules/CheckSceneDupeDependenciesTests.cs b/Tests/Editor/AnalyzeRules/CheckSceneDupeDependenciesTests.cs index e13ce99b..ab1ea39b 100644 --- a/Tests/Editor/AnalyzeRules/CheckSceneDupeDependenciesTests.cs +++ b/Tests/Editor/AnalyzeRules/CheckSceneDupeDependenciesTests.cs @@ -37,6 +37,8 @@ protected override void OnInit() var meshPrefabWithMaterial = prefabWithMaterial.AddComponent(); meshPrefabWithMaterial.material = AssetDatabase.LoadAssetAtPath(k_CheckDupeMyMaterial); + prefabA.AddComponent(); + #if UNITY_2018_3_OR_NEWER PrefabUtility.SaveAsPrefabAsset(prefabA, k_CheckDupePrefabA); PrefabUtility.SaveAsPrefabAsset(prefabB, k_CheckDupePrefabB); @@ -117,5 +119,36 @@ public void CheckSceneDupe_AllSceneToBundleDependenciesAreReturned() //Cleanup EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); } + + [Test] + public void CheckSceneDupe_SceneDependenciesDoNotIncludeScripts() + { + Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); + GameObject go = AssetDatabase.LoadAssetAtPath(k_CheckDupePrefabA); + GameObject g = PrefabUtility.InstantiatePrefab(go, scene) as GameObject; + g.AddComponent(); + EditorSceneManager.SaveScene(scene, k_ScenePath); + + var rule = new CheckSceneDupeDependencies(); + + EditorBuildSettingsScene editorScene = new EditorBuildSettingsScene(k_ScenePath, true); + rule.BuiltInResourcesToDependenciesMap(new string[] { editorScene.path }); + + Assert.IsTrue(rule.m_ResourcesToDependencies.ContainsKey(editorScene.path)); + bool containsAnyScripts = false; + foreach ( GUID guid in rule.m_ResourcesToDependencies[editorScene.path]) + { + string path = AssetDatabase.GUIDToAssetPath(guid.ToString()); + if (path.EndsWith(".cs") || path.EndsWith(".dll")) + { + containsAnyScripts = true; + break; + } + } + Assert.IsFalse(containsAnyScripts, "Scripts were included as a duplciate dependency"); + + //Cleanup + EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); + } } } diff --git a/Tests/Editor/Build/BuildLayoutGenerationTaskTests.cs b/Tests/Editor/Build/BuildLayoutGenerationTaskTests.cs index 273ac511..f7d89f7f 100644 --- a/Tests/Editor/Build/BuildLayoutGenerationTaskTests.cs +++ b/Tests/Editor/Build/BuildLayoutGenerationTaskTests.cs @@ -36,8 +36,8 @@ public void OneTimeSetup() public void Setup() { TempPath = kTempPath + (ExecCount++).ToString(); - if (File.Exists(BuildLayoutGenerationTask.kLayoutTextFile)) - File.Delete(BuildLayoutGenerationTask.kLayoutTextFile); + if (File.Exists(BuildLayoutGenerationTask.m_LayoutTextFile)) + File.Delete(BuildLayoutGenerationTask.m_LayoutTextFile); m_PrevGenerateBuildLayout = ProjectConfigData.GenerateBuildLayout; BuildScriptPackedMode.s_SkipCompilePlayerScripts = true; ProjectConfigData.GenerateBuildLayout = true; @@ -294,7 +294,7 @@ public void WhenSlimWriteResultsIsTrue_LayoutStillGenerated() BuildAndExtractLayout(); } finally { ScriptableBuildPipeline.slimWriteResults = prevSlim; } - FileAssert.Exists(BuildLayoutGenerationTask.kLayoutTextFile); + FileAssert.Exists(BuildLayoutGenerationTask.m_LayoutTextFile); } [Test] @@ -303,7 +303,7 @@ public void WhenBuildLayoutIsDisabled_BuildLayoutIsNotGenerated() ProjectConfigData.GenerateBuildLayout = false; CreateAddressablePrefab("p1", CreateGroup("Group1")); BuildAndExtractLayout(); - FileAssert.DoesNotExist(BuildLayoutGenerationTask.kLayoutTextFile); + FileAssert.DoesNotExist(BuildLayoutGenerationTask.m_LayoutTextFile); } [Test] diff --git a/Tests/Editor/Build/BuildScriptPackedTests.cs b/Tests/Editor/Build/BuildScriptPackedTests.cs index f4460aee..eaddd25e 100644 --- a/Tests/Editor/Build/BuildScriptPackedTests.cs +++ b/Tests/Editor/Build/BuildScriptPackedTests.cs @@ -318,7 +318,7 @@ public void HandleBundlesNaming_NamesShouldAlwaysBeUnique(List var group = Settings.CreateGroup("PackedTest", false, false, false, null, typeof(BundledAssetGroupSchema)); var bundleToAssetGroup = new Dictionary(); - m_BuildScript.HandleDuplicateBundleNames(bundleBuilds, bundleToAssetGroup, group.Guid, out var uniqueNames); + List uniqueNames = BuildScriptPackedMode.HandleDuplicateBundleNames(bundleBuilds, bundleToAssetGroup, group.Guid); var uniqueNamesInBundleBuilds = bundleBuilds.Select(b => b.assetBundleName).Distinct(); Assert.AreEqual(bundleBuilds.Count, uniqueNames.Count()); diff --git a/Tests/Editor/Build/BuildScriptTests.cs b/Tests/Editor/Build/BuildScriptTests.cs index ea70fd16..b74ec32b 100644 --- a/Tests/Editor/Build/BuildScriptTests.cs +++ b/Tests/Editor/Build/BuildScriptTests.cs @@ -341,9 +341,10 @@ public void BuildScriptBaseWriteBuildLog_WhenDirectoryDoesNotExist_DirectoryCrea public void Building_CreatesPerformanceReportWithMetaData() { Settings.BuildPlayerContentImpl(); - string text = File.ReadAllText("Library/com.unity.addressables/AddressablesBuildTEP.json"); + string path = Addressables.LibraryPath + "AddressablesBuildTEP.json"; + FileAssert.Exists(path); + string text = File.ReadAllText(path); StringAssert.Contains("com.unity.addressables", text); - FileAssert.Exists("Library/com.unity.addressables/AddressablesBuildTEP.json"); } #endif @@ -386,7 +387,7 @@ public void Build_GroupWithPlayerDataGroupSchemaAndBundledAssetGroupSchema_LogsE [Test] public void WhenBundleLocalCatalogEnabled_BuildScriptPacked_DoesNotCreatePerformanceLogReport() { - string logPath = $"Library/com.unity.addressables/aa/{PlatformMappingService.GetPlatform()}/buildlogtep.json"; + string logPath = $"Library/com.unity.addressables/aa/{PlatformMappingService.GetPlatformPathSubFolder()}/buildlogtep.json"; if (File.Exists(logPath)) File.Delete(logPath); diff --git a/Tests/Editor/ContentUpdateTests.cs b/Tests/Editor/ContentUpdateTests.cs index c8d0a24c..1b69ab03 100644 --- a/Tests/Editor/ContentUpdateTests.cs +++ b/Tests/Editor/ContentUpdateTests.cs @@ -39,7 +39,7 @@ public void CanCreateContentStateData() var op = Settings.ActivePlayerDataBuilder.BuildData(context); Assert.IsTrue(string.IsNullOrEmpty(op.Error), op.Error); - var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; var cacheData = ContentUpdateScript.LoadContentState(tempPath); Assert.NotNull(cacheData); Settings.RemoveGroup(group); @@ -71,7 +71,7 @@ public void ContentState_WithDisabledGroups_DoesNotInclude_EntriesFromGroup() var op = Settings.ActivePlayerDataBuilder.BuildData(context); Assert.IsTrue(string.IsNullOrEmpty(op.Error), op.Error); - var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; var cacheData = ContentUpdateScript.LoadContentState(tempPath); Assert.NotNull(cacheData); Assert.NotNull(cacheData.cachedInfos.FirstOrDefault(s => s.asset.guid.ToString() == m_AssetGUID)); @@ -80,7 +80,7 @@ public void ContentState_WithDisabledGroups_DoesNotInclude_EntriesFromGroup() context = new AddressablesDataBuilderInput(Settings); op = Settings.ActivePlayerDataBuilder.BuildData(context); Assert.IsTrue(string.IsNullOrEmpty(op.Error), op.Error); - tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; cacheData = ContentUpdateScript.LoadContentState(tempPath); Assert.NotNull(cacheData); Assert.IsNull(cacheData.cachedInfos.FirstOrDefault(s => s.asset.guid.ToString() == m_AssetGUID)); @@ -124,7 +124,7 @@ public void PrepareContentUpdate() EditorUtility.SetDirty(obj); #endif AssetDatabase.SaveAssets(); - var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; var modifiedEntries = ContentUpdateScript.GatherModifiedEntries(Settings, tempPath); Assert.IsNotNull(modifiedEntries); Assert.GreaterOrEqual(modifiedEntries.Count, 1); @@ -136,6 +136,137 @@ public void PrepareContentUpdate() Settings.RemoveGroup(group); } + [Test] + public void GatherModifiedEntries_WhenDependencyBundleNameIsSame_DependencyIsNotFlaggedAsModified() + { + // Create assets + GameObject mainObject = new GameObject("mainObject"); + Material mat = new Material(Shader.Find("Transparent/Diffuse")); + mainObject.AddComponent().material = mat; + + string mainAssetPath = GetAssetPath("mainObject.prefab"); + string staticAssetPath = GetAssetPath("staticObject.mat"); + + AssetDatabase.CreateAsset(mat, staticAssetPath); + PrefabUtility.SaveAsPrefabAsset(mainObject, mainAssetPath); + AssetDatabase.SaveAssets(); + + // Create addressables + AddressableAssetGroup mainAssetGroup = Settings.CreateGroup("PrefabGroup", false, false, false, null); + AddressableAssetGroup staticContentGroup = Settings.CreateGroup("MatGroup", false, false, false, null); + + var schema = mainAssetGroup.AddSchema(); + schema.BuildPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalBuildPath); + schema.LoadPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalLoadPath); + schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; + mainAssetGroup.AddSchema().StaticContent = true; + + schema = staticContentGroup.AddSchema(); + schema.BuildPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalBuildPath); + schema.LoadPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalLoadPath); + schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; + staticContentGroup.AddSchema().StaticContent = true; + + AddressableAssetEntry mainEntry = Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(mainAssetPath), mainAssetGroup); + AddressableAssetEntry staticEntry = Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(staticAssetPath), staticContentGroup); + + // Build + var context = new AddressablesDataBuilderInput(Settings); + Settings.ActivePlayerDataBuilder.BuildData(context); + + // Modify assets + var mainAsset = AssetDatabase.LoadAssetAtPath(mainAssetPath); + mainAsset.GetComponent().SetPositionAndRotation(new Vector3(10, 10, 10), Quaternion.identity); +#if UNITY_2018_3_OR_NEWER + PrefabUtility.SavePrefabAsset(mainAsset); +#else + EditorUtility.SetDirty(mainAsset); +#endif + AssetDatabase.SaveAssets(); + + // Test + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; + var modifiedEntries = ContentUpdateScript.GatherModifiedEntries(Settings, tempPath); + + Assert.AreEqual(1, modifiedEntries.Count); + Assert.AreSame(modifiedEntries[0], mainEntry); + + // Cleanup + GameObject.DestroyImmediate(mainObject); + + Settings.RemoveGroup(mainAssetGroup); + Settings.RemoveGroup(staticContentGroup); + + AssetDatabase.DeleteAsset(mainAssetPath); + AssetDatabase.DeleteAsset(staticAssetPath); + } + + [Test] + public void GatherModifiedEntries_WhenDependencyBundleNameIsChanged_DependencyIsFlaggedAsModified() + { + // Create assets + GameObject mainObject = new GameObject("mainObject"); + Material mat = new Material(Shader.Find("Transparent/Diffuse")); + mainObject.AddComponent().material = mat; + + string mainAssetPath = GetAssetPath("mainObject.prefab"); + string staticAssetPath = GetAssetPath("staticObject.mat"); + + AssetDatabase.CreateAsset(mat, staticAssetPath); + PrefabUtility.SaveAsPrefabAsset(mainObject, mainAssetPath); + AssetDatabase.SaveAssets(); + + // Create addressables + AddressableAssetGroup mainAssetGroup = Settings.CreateGroup("PrefabGroup2", false, false, false, null); + AddressableAssetGroup staticContentGroup = Settings.CreateGroup("MatGroup2", false, false, false, null); + + var schema = mainAssetGroup.AddSchema(); + schema.BuildPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalBuildPath); + schema.LoadPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalLoadPath); + schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; + mainAssetGroup.AddSchema().StaticContent = true; + + schema = staticContentGroup.AddSchema(); + schema.BuildPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalBuildPath); + schema.LoadPath.SetVariableByName(Settings, AddressableAssetSettings.kLocalLoadPath); + schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; + staticContentGroup.AddSchema().StaticContent = true; + + AddressableAssetEntry mainEntry = Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(mainAssetPath), mainAssetGroup); + AddressableAssetEntry staticEntry = Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(staticAssetPath), staticContentGroup); + + // Build + var context = new AddressablesDataBuilderInput(Settings); + Settings.ActivePlayerDataBuilder.BuildData(context); + + // Modify assets + var mainAsset = AssetDatabase.LoadAssetAtPath(mainAssetPath); + mainAsset.GetComponent().SetPositionAndRotation(new Vector3(10, 10, 10), Quaternion.identity); +#if UNITY_2018_3_OR_NEWER + PrefabUtility.SavePrefabAsset(mainAsset); +#else + EditorUtility.SetDirty(mainAsset); +#endif + staticContentGroup.GetSchema().InternalBundleIdMode = BundledAssetGroupSchema.BundleInternalIdMode.GroupGuidProjectIdEntriesHash; + AssetDatabase.SaveAssets(); + + // Test + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; + var modifiedEntries = ContentUpdateScript.GatherModifiedEntries(Settings, tempPath); + + Assert.AreEqual(2, modifiedEntries.Count); + Assert.IsTrue(modifiedEntries.Contains(staticEntry)); + + // Cleanup + GameObject.DestroyImmediate(mainObject); + + Settings.RemoveGroup(mainAssetGroup); + Settings.RemoveGroup(staticContentGroup); + + AssetDatabase.DeleteAsset(mainAssetPath); + AssetDatabase.DeleteAsset(staticAssetPath); + } + [Test] public void GetStaticContentDependenciesOfModifiedEntries_FlagsEntryDependencies_WithStaticContentEnabled() { @@ -171,7 +302,7 @@ public void GetStaticContentDependenciesOfModifiedEntries_FlagsEntryDependencies { {mainEntry, new List() } }; - ContentUpdateScript.GetStaticContentDependenciesForEntries(Settings, ref staticDependencies); + ContentUpdateScript.GetStaticContentDependenciesForEntries(Settings, ref staticDependencies, null); Assert.AreEqual(1, staticDependencies.Count); Assert.AreEqual(1, staticDependencies[mainEntry].Count); @@ -222,7 +353,7 @@ public void GetStaticContentDependenciesOfModifiedEntries_DoesNotFlagEntryDepend { {mainEntry, new List() } }; - ContentUpdateScript.GetStaticContentDependenciesForEntries(Settings, ref staticDependencies); + ContentUpdateScript.GetStaticContentDependenciesForEntries(Settings, ref staticDependencies, null); Assert.AreEqual(1, staticDependencies.Count); Assert.AreEqual(0, staticDependencies[mainEntry].Count); @@ -269,7 +400,7 @@ public void WhenContentUpdated_NewCatalogRetains_OldCatalogBundleLoadData() var origLocator = GetLocatorFromCatalog(op.FileRegistry.GetFilePaths()); Assert.NotNull(origLocator); - var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; var contentState = ContentUpdateScript.LoadContentState(tempPath); Assert.NotNull(contentState); Assert.NotNull(contentState.cachedBundles); @@ -366,7 +497,7 @@ public void BuildContentUpdate_DoesNotDeleteBuiltData() var op = Settings.ActivePlayerDataBuilder.BuildData(context); Assert.IsTrue(string.IsNullOrEmpty(op.Error), op.Error); - var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; + var tempPath = Path.GetDirectoryName(Application.dataPath) + "/" + Addressables.LibraryPath + PlatformMappingService.GetPlatformPathSubFolder() + "/addressables_content_state.bin"; ContentUpdateScript.BuildContentUpdate(Settings, tempPath); Assert.IsTrue(Directory.Exists(Addressables.BuildPath)); Settings.BuildRemoteCatalog = oldSetting; @@ -462,61 +593,47 @@ public void ContentUpdateScenes_PackedSeperately_MarksNoAdditionalScenes() private ContentUpdateScript.ContentUpdateContext GetContentUpdateContext(string contentUpdateTestAssetGUID, string contentUpdateTestCachedAssetHash, string contentUpdateTestNewInternalBundleName, string contentUpdateTestNewBundleName, string contentUpdateTestCachedBundlePath, string contentUpdateTestGroupGuid, string contentUpdateTestFileName) { - Dictionary bundleToInternalBundle = new Dictionary() - { - { - contentUpdateTestNewInternalBundleName, - contentUpdateTestNewBundleName - } + var context = new ContentUpdateScript.ContentUpdateContext() + { + WriteData = new BundleWriteData(), + BundleToInternalBundleIdMap = new Dictionary(), + GuidToPreviousAssetStateMap = new Dictionary(), + IdToCatalogDataEntryMap = new Dictionary(), + ContentState = new AddressablesContentState(), + PreviousAssetStateCarryOver = new List(), + Registry = new FileRegistry() }; + AddToContentUpdateContext(context, contentUpdateTestAssetGUID, contentUpdateTestCachedAssetHash, + contentUpdateTestNewInternalBundleName, contentUpdateTestNewBundleName, contentUpdateTestCachedBundlePath, contentUpdateTestGroupGuid, contentUpdateTestFileName); + return context; + } - Dictionary guidToCachedState = new Dictionary() - { - //entry 1 + private void AddToContentUpdateContext(ContentUpdateScript.ContentUpdateContext context, string contentUpdateTestAssetGUID, string contentUpdateTestCachedAssetHash, + string contentUpdateTestNewInternalBundleName, string contentUpdateTestNewBundleName, string contentUpdateTestCachedBundlePath, string contentUpdateTestGroupGuid, string contentUpdateTestFileName) + { + context.BundleToInternalBundleIdMap.Add(contentUpdateTestNewInternalBundleName, contentUpdateTestNewBundleName); + context.GuidToPreviousAssetStateMap.Add(contentUpdateTestAssetGUID, + new CachedAssetState() { - contentUpdateTestAssetGUID, new CachedAssetState() + bundleFileId = contentUpdateTestCachedBundlePath, + asset = new AssetState() { - bundleFileId = contentUpdateTestCachedBundlePath, - asset = new AssetState() - { - guid = new GUID(contentUpdateTestAssetGUID), - hash = Hash128.Parse(contentUpdateTestCachedAssetHash) - }, - dependencies = new AssetState[] {}, - data = null, - groupGuid = contentUpdateTestGroupGuid - } + guid = new GUID(contentUpdateTestAssetGUID), + hash = Hash128.Parse(contentUpdateTestCachedAssetHash) + }, + dependencies = new AssetState[] {}, + data = null, + groupGuid = contentUpdateTestGroupGuid } - }; - - Dictionary idToCatalogEntryMap = new Dictionary() - { - //bundle entry - { contentUpdateTestNewBundleName, - new ContentCatalogDataEntry(typeof(IAssetBundleResource), contentUpdateTestNewBundleName, - typeof(AssetBundleProvider).FullName, new[] { contentUpdateTestNewBundleName})}, - //asset entry - { - contentUpdateTestAssetGUID, - new ContentCatalogDataEntry(typeof(IResourceLocation), contentUpdateTestAssetGUID, typeof(BundledAssetProvider).FullName, new[] {contentUpdateTestAssetGUID}) - } - }; + ); - IBundleWriteData writeData = new BundleWriteData(); - writeData.AssetToFiles.Add(new GUID(contentUpdateTestAssetGUID), new List() { contentUpdateTestFileName }); - writeData.FileToBundle.Add(contentUpdateTestFileName, contentUpdateTestNewInternalBundleName); + context.IdToCatalogDataEntryMap.Add(contentUpdateTestNewBundleName, + new ContentCatalogDataEntry(typeof(IAssetBundleResource), contentUpdateTestNewBundleName, typeof(AssetBundleProvider).FullName, new[] { contentUpdateTestNewBundleName })); + context.IdToCatalogDataEntryMap.Add(contentUpdateTestAssetGUID, + new ContentCatalogDataEntry(typeof(IAssetBundleResource), contentUpdateTestAssetGUID, typeof(AssetBundleProvider).FullName, new[] { contentUpdateTestAssetGUID })); - ContentUpdateScript.ContentUpdateContext context = new ContentUpdateScript.ContentUpdateContext() - { - WriteData = writeData, - BundleToInternalBundleIdMap = bundleToInternalBundle, - GuidToPreviousAssetStateMap = guidToCachedState, - IdToCatalogDataEntryMap = idToCatalogEntryMap, - ContentState = new AddressablesContentState(), - PreviousAssetStateCarryOver = new List(), - Registry = new FileRegistry() - }; - return context; + context.WriteData.AssetToFiles.Add(new GUID(contentUpdateTestAssetGUID), new List() { contentUpdateTestFileName }); + context.WriteData.FileToBundle.Add(contentUpdateTestFileName, contentUpdateTestNewInternalBundleName); } private AddressableAssetEntry CreateAssetEntry(string guid, AddressableAssetGroup group) @@ -938,5 +1055,87 @@ public void ApplyAssetEntryUpdates_PreviousStateDependencies_SetInCatalogEntry() Settings.RemoveGroup(group); } + + [Test] + public void ApplyAssetEntryUpdates_WhenAssetAndDependencyAreModifiedAndInSeparateGroups_SetCatalogEntryToCachedBundles() + { + GUID depAssetGuid = GUID.Generate(); + string oldDepGroupCachedAssetHash = "1888888888888888888"; + string newDepGroupCachedAssetHash = "1188888888888888888"; + string depGroupNewInternalBundleName = "bundle2"; + string depGroupNewBundleName = "fullbundlepath2"; + string depGroupCachedBundlePath = "cachedBundle2"; + string depGroupFileName = "testfile2"; + + AddressableAssetGroup group = Settings.CreateGroup("ContentUpdateTests", false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema)); + string contentUpdateTestGroupGuid = GUID.Generate().ToString(); + + AddressableAssetGroup depGroup = Settings.CreateGroup("ContentUpdateTests2", false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema)); + GUID depGroupGuid = GUID.Generate(); + + group.GetSchema().StaticContent = false; + AddressableAssetEntry assetEntry = CreateAssetEntry(m_ContentUpdateTestAssetGUID, group); + group.AddAssetEntry(assetEntry); + + depGroup.GetSchema().StaticContent = false; + AddressableAssetEntry depAssetEntry = CreateAssetEntry(depAssetGuid.ToString(), depGroup); + depGroup.AddAssetEntry(assetEntry); + + ContentUpdateScript.ContentUpdateContext context = GetContentUpdateContext(m_ContentUpdateTestAssetGUID, k_ContentUpdateTestCachedAssetHash, + k_ContentUpdateTestNewInternalBundleName, k_ContentUpdateTestNewBundleName, + k_ContentUpdateTestCachedBundlePath, contentUpdateTestGroupGuid, k_ContentUpdateTestFileName); + + AddToContentUpdateContext(context, depAssetGuid.ToString(), oldDepGroupCachedAssetHash, + depGroupNewInternalBundleName, depGroupNewBundleName, + depGroupCachedBundlePath, depGroupGuid.ToString(), depGroupFileName); + + var previousDep = new AssetState() + { + guid = depAssetGuid, + hash = Hash128.Parse(oldDepGroupCachedAssetHash) + }; + + var currentDep = new AssetState() + { + guid = GUID.Generate(), + hash = Hash128.Parse(newDepGroupCachedAssetHash) + }; + + context.GuidToPreviousAssetStateMap[m_ContentUpdateTestAssetGUID].dependencies = new AssetState[] + { + previousDep + }; + + context.IdToCatalogDataEntryMap[m_ContentUpdateTestAssetGUID].Dependencies.Add(currentDep); + + var ops = new List() + { + new RevertUnchangedAssetsToPreviousAssetState.AssetEntryRevertOperation() + { + PreviousBuildPath = k_ContentUpdateTestCachedBundlePath, + AssetEntry = assetEntry, + BundleCatalogEntry = context.IdToCatalogDataEntryMap[m_ContentUpdateTestAssetGUID], + CurrentBuildPath = k_ContentUpdateTestNewBundleName, + PreviousAssetState = context.GuidToPreviousAssetStateMap[m_ContentUpdateTestAssetGUID] + }, + new RevertUnchangedAssetsToPreviousAssetState.AssetEntryRevertOperation() + { + PreviousBuildPath = depGroupCachedBundlePath, + AssetEntry = depAssetEntry, + BundleCatalogEntry = context.IdToCatalogDataEntryMap[depAssetGuid.ToString()], + CurrentBuildPath = depGroupNewBundleName, + PreviousAssetState = context.GuidToPreviousAssetStateMap[depAssetGuid.ToString()] + } + }; + + var locations = new List(); + RevertUnchangedAssetsToPreviousAssetState.ApplyAssetEntryUpdates(ops, "BundleProvider", locations, context); + + Assert.AreEqual(k_ContentUpdateTestCachedBundlePath, context.IdToCatalogDataEntryMap[m_ContentUpdateTestAssetGUID].InternalId); + Assert.AreEqual(depGroupCachedBundlePath, context.IdToCatalogDataEntryMap[depAssetGuid.ToString()].InternalId); + + Settings.RemoveGroup(group); + Settings.RemoveGroup(depGroup); + } } } diff --git a/Tests/Editor/GroupSchemaTests.cs b/Tests/Editor/GroupSchemaTests.cs index 164b0658..dbe16cd5 100644 --- a/Tests/Editor/GroupSchemaTests.cs +++ b/Tests/Editor/GroupSchemaTests.cs @@ -164,6 +164,7 @@ public void ModifyingGroupName_ChangesSchemaAssetPath() // Set up var group = Settings.CreateGroup("OldTestGroup", false, false, false, null); var testSchema = group.AddSchema(); + AssetDatabase.SaveAssets(); string testSchemaFilename = ObjectToFilename(testSchema); Assert.IsTrue(testSchemaFilename.Contains("OldTestGroup")); diff --git a/Tests/Runtime/AddressablesIntegrationTests.cs b/Tests/Runtime/AddressablesIntegrationTests.cs index e9013dbc..6375ee04 100644 --- a/Tests/Runtime/AddressablesIntegrationTests.cs +++ b/Tests/Runtime/AddressablesIntegrationTests.cs @@ -29,8 +29,8 @@ internal abstract partial class AddressablesIntegrationTests : IPrebuildSetup protected abstract string TypeName { get; } protected virtual string PathFormat { get { return "Assets/{0}_AssetsToDelete_{1}"; } } - protected virtual string GetRuntimePath(string testType, string suffix) { return string.Format("{0}Library/com.unity.addressables/settings_{1}_TEST_{2}.json", "file://{UnityEngine.Application.dataPath}/../", testType, suffix); } - protected virtual string GetCatalogPath(string testType, string suffix) { return string.Format("{0}Library/com.unity.addressables/catalog_{1}_TEST_{2}.json", "file://{UnityEngine.Application.dataPath}/../", testType, suffix); } + protected virtual string GetRuntimePath(string testType, string suffix) { return string.Format("{0}" + Addressables.LibraryPath + "settings_{1}_TEST_{2}.json", "file://{UnityEngine.Application.dataPath}/../", testType, suffix); } + protected virtual string GetCatalogPath(string testType, string suffix) { return string.Format("{0}" + Addressables.LibraryPath + "catalog_{1}_TEST_{2}.json", "file://{UnityEngine.Application.dataPath}/../", testType, suffix); } protected virtual ILocationSizeData CreateLocationSizeData(string name, long size, uint crc, string hash) { return null; } private object AssetReferenceObjectKey { get { return m_PrefabKeysList.FirstOrDefault(s => s.ToString().Contains("AssetReferenceBehavior")); } } diff --git a/Tests/Runtime/AddressablesIntegrationTestsImpl.cs b/Tests/Runtime/AddressablesIntegrationTestsImpl.cs index 2676a613..094e8b4d 100644 --- a/Tests/Runtime/AddressablesIntegrationTestsImpl.cs +++ b/Tests/Runtime/AddressablesIntegrationTestsImpl.cs @@ -139,7 +139,59 @@ public IEnumerator CanLoadSpriteByName() Assert.AreEqual("topleft", op2.Result.name); op2.Release(); } + + [UnityTest] + public IEnumerator CanLoadFromFolderEntry_SpriteAtlas() + { + //Setup + yield return Init(); + + var op = m_Addressables.LoadAssetAsync("folderEntry/atlas.spriteatlas"); + yield return op; + Assert.IsNotNull(op.Result); + Assert.AreEqual(typeof(SpriteAtlas), op.Result.GetType()); + op.Release(); + } + + [UnityTest] + public IEnumerator CanLoadFromFolderEntry_SpriteFromSpriteAtlas() + { + //Setup + yield return Init(); + + var op = m_Addressables.LoadAssetAsync("folderEntry/atlas.spriteatlas[sprite]"); + yield return op; + Assert.IsNotNull(op.Result); + Assert.AreEqual(typeof(Sprite), op.Result.GetType()); + op.Release(); + } + + [UnityTest] + public IEnumerator CanLoadFromFolderEntry_Texture() + { + //Setup + yield return Init(); + + var op = m_Addressables.LoadAssetAsync("folderEntry/spritesheet.png"); + yield return op; + Assert.IsNotNull(op.Result); + Assert.AreEqual(typeof(Texture2D), op.Result.GetType()); + op.Release(); + } + + [UnityTest] + public IEnumerator CanLoadFromFolderEntry_SpriteFromTexture() + { + //Setup + yield return Init(); + var op = m_Addressables.LoadAssetAsync("folderEntry/spritesheet.png[topleft]"); + yield return op; + Assert.IsNotNull(op.Result); + Assert.AreEqual(typeof(Sprite), op.Result.GetType()); + op.Release(); + } + [UnityTest] public IEnumerator CanLoadAllSpritesAsArray() { @@ -382,14 +434,15 @@ public IEnumerator LoadContentCatalogAsync_SetsUpLocalAndRemoteLocations() public IEnumerator LoadingContentCatalogTwice_DoesNotThrowException_WhenHandleIsntReleased() { yield return Init(); - + string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath); Directory.CreateDirectory(kCatalogFolderPath); if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null) { #if UNITY_EDITOR - ContentCatalogData data = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData data = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePath, JsonUtility.ToJson(data)); #else @@ -404,7 +457,7 @@ public IEnumerator LoadingContentCatalogTwice_DoesNotThrowException_WhenHandleIs baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath; File.Copy(baseCatalogPath, fullRemotePath); } - + var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; @@ -424,14 +477,15 @@ public IEnumerator LoadingContentCatalogTwice_DoesNotThrowException_WhenHandleIs public IEnumerator LoadingContentCatalogWithCacheTwice_DoesNotThrowException_WhenHandleIsntReleased() { yield return Init(); - + string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath); Directory.CreateDirectory(kCatalogFolderPath); if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null) { #if UNITY_EDITOR - ContentCatalogData data = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData data = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePath, JsonUtility.ToJson(data)); #else @@ -447,7 +501,7 @@ public IEnumerator LoadingContentCatalogWithCacheTwice_DoesNotThrowException_Whe File.Copy(baseCatalogPath, fullRemotePath); } WriteHashFileForCatalog(fullRemotePath, "123"); - + var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; @@ -495,14 +549,15 @@ private string WriteHashFileForCatalog(string catalogPath, string hash) public IEnumerator LoadingContentCatalog_CachesCatalogData_IfValidHashFound() { yield return Init(); - + string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath); Directory.CreateDirectory(kCatalogFolderPath); if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null) { #if UNITY_EDITOR - ContentCatalogData data = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData data = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePath, JsonUtility.ToJson(data)); #else @@ -521,7 +576,7 @@ public IEnumerator LoadingContentCatalog_CachesCatalogData_IfValidHashFound() var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; - + string fullRemoteHashPath = fullRemotePath.Replace(".json", ".hash"); string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPath.GetHashCode() + fullRemoteHashPath.Substring(fullRemoteHashPath.LastIndexOf("."))); string cachedHashPath = cachedDataPath.Replace(".json", ".hash"); @@ -534,12 +589,12 @@ public IEnumerator LoadingContentCatalog_CachesCatalogData_IfValidHashFound() File.Delete(cachedDataPath); File.Delete(cachedHashPath); } - + [UnityTest] public IEnumerator LoadingContentCatalog_CachesCatalogData_ForTwoCatalogsWithSameName() { yield return Init(); - + string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath); string fullRemotePathTwo = Path.Combine(kCatalogFolderPath, "secondCatalog", kCatalogRemotePath); Directory.CreateDirectory(kCatalogFolderPath); @@ -547,8 +602,9 @@ public IEnumerator LoadingContentCatalog_CachesCatalogData_ForTwoCatalogsWithSam if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null) { #if UNITY_EDITOR - ContentCatalogData data = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData data = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePath, JsonUtility.ToJson(data)); File.WriteAllText(fullRemotePathTwo, JsonUtility.ToJson(data)); @@ -564,26 +620,27 @@ public IEnumerator LoadingContentCatalog_CachesCatalogData_ForTwoCatalogsWithSam baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath; File.Copy(baseCatalogPath, fullRemotePath); } - + #if UNITY_EDITOR - ContentCatalogData catalogData = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData catalogData = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePathTwo, JsonUtility.ToJson(catalogData)); - + WriteHashFileForCatalog(fullRemotePath, "123"); WriteHashFileForCatalog(fullRemotePathTwo, "123"); #else - UnityEngine.Debug.Log($"Skipping test {nameof(LoadingContentCatalog_CachesCatalogData_IfValidHashFound)} due to missing CatalogLocation."); - yield break; + UnityEngine.Debug.Log($"Skipping test {nameof(LoadingContentCatalog_CachesCatalogData_IfValidHashFound)} due to missing CatalogLocation."); + yield break; #endif - + var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; - + var op2 = m_Addressables.LoadContentCatalogAsync(fullRemotePathTwo, false); yield return op2; - + string fullRemoteHashPath = fullRemotePath.Replace(".json", ".hash"); string fullRemoteHashPathTwo = fullRemotePathTwo.Replace(".json", ".hash"); string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPath.GetHashCode() + fullRemoteHashPath.Substring(fullRemoteHashPath.LastIndexOf("."))); @@ -605,20 +662,21 @@ public IEnumerator LoadingContentCatalog_CachesCatalogData_ForTwoCatalogsWithSam File.Delete(cachedDataPathTwo); File.Delete(cachedHashPathTwo); } - + [UnityTest] public IEnumerator LoadingContentCatalog_IfNoCachedHashFound_Succeeds() { yield return Init(); - + ResourceManager.ExceptionHandler = m_PrevHandler; string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath); Directory.CreateDirectory(kCatalogFolderPath); if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null) { #if UNITY_EDITOR - ContentCatalogData data = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData data = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePath, JsonUtility.ToJson(data)); #else @@ -634,7 +692,7 @@ public IEnumerator LoadingContentCatalog_IfNoCachedHashFound_Succeeds() File.Copy(baseCatalogPath, fullRemotePath); } WriteHashFileForCatalog(fullRemotePath, "123"); - + string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + Path.GetFileName(kCatalogRemotePath)); string cachedHashPath = cachedDataPath.Replace(".json", ".hash"); if (File.Exists(cachedDataPath)) @@ -643,11 +701,11 @@ public IEnumerator LoadingContentCatalog_IfNoCachedHashFound_Succeeds() File.Delete(cachedHashPath); var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; - + Assert.IsTrue(op1.IsValid()); Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status); Assert.NotNull(op1.Result); - + // Cleanup Addressables.Release(op1); Directory.Delete(kCatalogFolderPath, true); @@ -659,15 +717,16 @@ public IEnumerator LoadingContentCatalog_IfNoCachedHashFound_Succeeds() public IEnumerator LoadingContentCatalog_IfNoHashFileForCatalog_DoesntThrowException() { yield return Init(); - + ResourceManager.ExceptionHandler = m_PrevHandler; string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath); Directory.CreateDirectory(kCatalogFolderPath); if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null) { #if UNITY_EDITOR - ContentCatalogData data = new ContentCatalogData(new List{ - new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new []{"key"}) + ContentCatalogData data = new ContentCatalogData(new List + { + new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"}) }, "test_catalog"); File.WriteAllText(fullRemotePath, JsonUtility.ToJson(data)); #else @@ -691,19 +750,19 @@ public IEnumerator LoadingContentCatalog_IfNoHashFileForCatalog_DoesntThrowExcep File.Delete(cachedHashPath); var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; - + Assert.IsTrue(op1.IsValid()); Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status); Assert.NotNull(op1.Result); Assert.IsFalse(File.Exists(cachedHashPath)); - + // Cleanup Addressables.Release(op1); Directory.Delete(kCatalogFolderPath, true); File.Delete(cachedDataPath); File.Delete(cachedHashPath); } - + [UnityTest] public IEnumerator IResourceLocationComparing_SameKeySameTypeDifferentInternalId_ReturnsFalse() { @@ -751,7 +810,7 @@ public IEnumerator LoadingContentCatalog_UpdatesCachedData_IfHashFileUpdates() var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false); yield return op1; m_Addressables.Release(op1); - + Assert.IsTrue(File.Exists(cachedDataPath)); Assert.IsTrue(File.Exists(cachedHashPath)); Assert.AreEqual("123", File.ReadAllText(cachedHashPath)); @@ -2275,7 +2334,9 @@ public IEnumerator AssetBundleRequestOptions_ComputesCorrectSize_WhenLocationDoe //Test Assert.AreEqual(size, abro.ComputeSize(location, m_Addressables.ResourceManager)); CreateFakeCachedBundle(bundleName, hash.ToString()); - Assert.AreEqual(0, abro.ComputeSize(location, m_Addressables.ResourceManager)); + + // No hash, so the bundle should be downloaded + Assert.AreEqual(size, abro.ComputeSize(location, m_Addressables.ResourceManager)); //Cleanup Caching.ClearAllCachedVersions(bundleName); @@ -2401,7 +2462,7 @@ public IEnumerator Autorelease_True_ClearDependencyCacheListIResourceLocs_WithCh yield return Init(); //This is to make sure we use the ShouldChainRequest var dumbUpdate = new DumbUpdateOperation(); - m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle>(dumbUpdate); + m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle>(dumbUpdate); List locations = new List() { diff --git a/Tests/Runtime/AddressablesTestFixture.cs b/Tests/Runtime/AddressablesTestFixture.cs index 7aa17443..aaf37aa1 100644 --- a/Tests/Runtime/AddressablesTestFixture.cs +++ b/Tests/Runtime/AddressablesTestFixture.cs @@ -111,7 +111,7 @@ protected virtual void RunBuilder(AddressableAssetSettings settings) var buildContext = new AddressablesDataBuilderInput(settings); buildContext.RuntimeSettingsFilename = "settings" + m_UniqueTestName + ".json"; buildContext.RuntimeCatalogFilename = "catalog" + m_UniqueTestName + ".json"; - buildContext.PathFormat = "{0}Library/com.unity.addressables/{1}_" + m_UniqueTestName + ".json"; + buildContext.PathFormat = "{0}" + Addressables.LibraryPath + "{1}_" + m_UniqueTestName + ".json"; if (BuildScriptMode == TestBuildScriptMode.PackedPlaymode) { IDataBuilder packedModeBuilder = GetBuilderOfType(settings, typeof(BuildScriptPackedMode)); @@ -157,7 +157,7 @@ string GetRuntimeAddressablesSettingsPath() } else { - return string.Format("{0}Library/com.unity.addressables/settings_{1}.json", "file://{UnityEngine.Application.dataPath}/../", m_UniqueTestName); + return string.Format("{0}" + Addressables.LibraryPath + "settings_{1}.json", "file://{UnityEngine.Application.dataPath}/../", m_UniqueTestName); } } @@ -194,4 +194,17 @@ internal static IEnumerator UnloadSceneFromHandler(AsyncOperationHandle op, AddressablesImpl addressables) + { + string sceneName = op.Result.Scene.name; + Assert.IsNotNull(sceneName); + var prevRefCount = op.ReferenceCount; + var unloadOp = addressables.UnloadSceneAsync(op, false); + yield return unloadOp; + Assert.AreEqual(AsyncOperationStatus.Succeeded, unloadOp.Status); + Assert.IsFalse(unloadOp.Result.Scene.isLoaded); + if(op.IsValid()) + Assert.AreEqual(prevRefCount-1,op.ReferenceCount); + } } diff --git a/Tests/Runtime/AddressablesTestUtilities.cs b/Tests/Runtime/AddressablesTestUtilities.cs index 31538665..e7ba1fb2 100644 --- a/Tests/Runtime/AddressablesTestUtilities.cs +++ b/Tests/Runtime/AddressablesTestUtilities.cs @@ -9,11 +9,13 @@ using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.TestTools; +using UnityEngine.U2D; #if UNITY_EDITOR using UnityEditor; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Build; using UnityEditor.AddressableAssets.Settings.GroupSchemas; +using UnityEditor.U2D; #endif static class AddressablesTestUtility @@ -135,6 +137,8 @@ static public void Setup(string testType, string pathFormat, string suffix) PrefabUtility.SaveAsPrefabAsset(go, hasBehaviorPath); settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(hasBehaviorPath), group, false, false); + + CreateFolderEntryAssets(RootFolder, settings, group); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); @@ -146,6 +150,58 @@ static public void Setup(string testType, string pathFormat, string suffix) } #if UNITY_EDITOR + static void CreateFolderEntryAssets(string RootFolder, AddressableAssetSettings settings, AddressableAssetGroup group) + { + AssetDatabase.CreateFolder(RootFolder, "folderEntry"); + string folderPath = RootFolder + "/folderEntry"; + + { + var texture = new Texture2D(32, 32); + var data = ImageConversion.EncodeToPNG(texture); + UnityEngine.Object.DestroyImmediate(texture); + AssetDatabase.GenerateUniqueAssetPath(RootFolder); + var spritePath = folderPath + "/spritesheet.png"; + File.WriteAllBytes(spritePath, data); + + AssetDatabase.ImportAsset(spritePath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate); + + var spriteGuid = AssetDatabase.AssetPathToGUID(spritePath); + var importer = (TextureImporter)AssetImporter.GetAtPath(spritePath); + importer.textureType = TextureImporterType.Sprite; + importer.spriteImportMode = SpriteImportMode.Multiple; + importer.spritesheet = new SpriteMetaData[] { new SpriteMetaData() { name = "topleft", pivot = Vector2.zero, rect = new Rect(0, 0, 16, 16) }, + new SpriteMetaData() { name = "botright", pivot = Vector2.zero, rect = new Rect(16, 16, 16, 16) }}; + importer.SaveAndReimport(); + } + + { + var texture = new Texture2D(32, 32); + var data = ImageConversion.EncodeToPNG(texture); + UnityEngine.Object.DestroyImmediate(texture); + + var spritePath = folderPath + "/sprite.png"; + File.WriteAllBytes(spritePath, data); + AssetDatabase.ImportAsset(spritePath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate); + + var spriteGuid = AssetDatabase.AssetPathToGUID(spritePath); + var importer = (TextureImporter) AssetImporter.GetAtPath(spritePath); + importer.textureType = TextureImporterType.Sprite; + importer.SaveAndReimport(); + + string atlasPath = folderPath + "/atlas.spriteatlas"; + var sa = new SpriteAtlas(); + AssetDatabase.CreateAsset(sa, atlasPath); + sa.Add(new UnityEngine.Object[] + { + AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(spriteGuid)) + }); + SpriteAtlasUtility.PackAtlases(new SpriteAtlas[] {sa}, EditorUserBuildSettings.activeBuildTarget, false); + } + + var folderEntry = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(folderPath), group, false, false); + folderEntry.address = "folderEntry"; + } + static string CreateAsset(string assetPath, string objectName) { GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube); @@ -168,7 +224,7 @@ static void RunBuilder(AddressableAssetSettings settings, string testType, strin var b = db as IDataBuilder; if (b.GetType().Name != testType) continue; - buildContext.PathFormat = "{0}Library/com.unity.addressables/{1}_" + testType + "_TEST_" + suffix + ".json"; + buildContext.PathFormat = "{0}" + Addressables.LibraryPath + "{1}_" + testType + "_TEST_" + suffix + ".json"; b.BuildData(buildContext); PlayerPrefs.SetString(Addressables.kAddressablesRuntimeDataPath + testType, PlayerPrefs.GetString(Addressables.kAddressablesRuntimeDataPath, "")); } diff --git a/Tests/Runtime/AsyncTaskTests.cs b/Tests/Runtime/AsyncTaskTests.cs new file mode 100644 index 00000000..2833e97f --- /dev/null +++ b/Tests/Runtime/AsyncTaskTests.cs @@ -0,0 +1,51 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.Util; +using UnityEngine.TestTools; + +namespace AddressableTests.AsyncTask +{ + public abstract class AsyncTaskTests : AddressablesTestFixture + { + [UnityTest] + public IEnumerator AsyncTask_MaintainsCorrectRefCountAfterCompletion() + { + AddressablesImpl impl = new AddressablesImpl(new DefaultAllocationStrategy()); + var op = impl.InitializeAsync(m_RuntimeSettingsPath); + var task = op.Task; + while (!task.IsCompleted) + yield return null; + yield return null; //need deferred callbacks to get called + Assert.IsFalse(op.IsValid()); + } + + [UnityTest] + public IEnumerator AsyncTask_DoesNotReturnNull_StressTest() + { + for (int i = 0; i < 100; i++) + { + AddressablesImpl impl = new AddressablesImpl(new DefaultAllocationStrategy()); + var op = impl.InitializeAsync(m_RuntimeSettingsPath); + var task = op.Task; + while (!task.IsCompleted) + yield return null; + yield return null; //need deferred callbacks to get called + Assert.IsNotNull(task.Result); + } + } + } +#if UNITY_EDITOR + class AsyncTaskTests_FastMode : AsyncTaskTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.Fast; } } } + + class AsyncTaskTests_VirtualMode : AsyncTaskTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.Virtual; } } } + + class AsyncTaskTests_PackedPlaymodeMode : AsyncTaskTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.PackedPlaymode; } } } +#endif + + [UnityPlatform(exclude = new[] { RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor, RuntimePlatform.LinuxEditor })] + class AsyncTaskTests_PackedMode : AsyncTaskTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.Packed; } } } +} \ No newline at end of file diff --git a/Tests/Runtime/AsyncTaskTests.cs.meta b/Tests/Runtime/AsyncTaskTests.cs.meta new file mode 100644 index 00000000..f3134841 --- /dev/null +++ b/Tests/Runtime/AsyncTaskTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f4d1d59e25085e489592b2342c86673 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Initialization/AddrRuntimePropertiesTests.cs b/Tests/Runtime/Initialization/AddrRuntimePropertiesTests.cs index bf5131d4..b540adad 100644 --- a/Tests/Runtime/Initialization/AddrRuntimePropertiesTests.cs +++ b/Tests/Runtime/Initialization/AddrRuntimePropertiesTests.cs @@ -1,6 +1,7 @@ using UnityEngine; using NUnit.Framework; using System; +using System.Collections.Generic; using UnityEngine.AddressableAssets.Initialization; namespace AddrRuntimePropertiesTests @@ -96,6 +97,50 @@ public void RuntimeProperties_EvaluateStringCanParseInExplicitOverride() Assert.AreEqual(expectedResult, actualResult); } + [Test] + [Timeout(1000)] + public void RuntimeProperties_CanDetectCyclicLoops() + { + string a = "[B]"; + string b = "[A]"; + string toEval = "Test_[A]_"; + string expectedResult = "Test_#ERROR-CyclicToken#_"; + + string actualResult = AddressablesRuntimeProperties.EvaluateString(toEval, '[', ']', s => + { + switch (s) + { + case "A": + return a; + case "B": + return b; + } + return ""; + }); + + Assert.AreEqual(expectedResult, actualResult); + } + + [Test] + [Timeout(1000)] + public void RuntimeProperties_CanEvaluateInnerProperties() + { + Dictionary stringLookup = new Dictionary(); + stringLookup.Add("B", "inner"); + stringLookup.Add("With_inner", "Success"); + string toEval = "Test_[With_[B]]"; + string expectedResult = "Test_Success"; + + string actualResult = AddressablesRuntimeProperties.EvaluateString(toEval, '[', ']', s => + { + if (stringLookup.TryGetValue(s, out string val)) + return val; + return ""; + }); + + Assert.AreEqual(expectedResult, actualResult); + } + [Test] public void RuntimeProperties_EvaluateStringIgnoresSingleDelim() { diff --git a/Tests/Runtime/Initialization/FastModeInitializationTests.cs b/Tests/Runtime/Initialization/FastModeInitializationTests.cs new file mode 100644 index 00000000..a76b44de --- /dev/null +++ b/Tests/Runtime/Initialization/FastModeInitializationTests.cs @@ -0,0 +1,58 @@ +using System.IO; +using NUnit.Framework; +#if UNITY_EDITOR +using UnityEditor.AddressableAssets.Settings; +#endif + +namespace AddressableTests.FastModeInitTests +{ +#if UNITY_EDITOR + public class FastModeInitializationTests : AddressablesTestFixture + { + protected override TestBuildScriptMode BuildScriptMode => TestBuildScriptMode.Fast; + + [TestCase(true)] + [TestCase(false)] + public void FastModeInit_EventViewerSetup_InitializesPostProfilerEventsValue(bool sendProfilerEvents) + { + //Setup + bool originalValue = ProjectConfigData.PostProfilerEvents; + ProjectConfigData.PostProfilerEvents = sendProfilerEvents; + var settings = AddressableAssetSettings.Create(Path.Combine(GetGeneratedAssetsPath(), "Settings"), "AddressableAssetSettings.Tests", false, true); + + //Test + FastModeInitializationOperation fmInit = new FastModeInitializationOperation(m_Addressables, settings); + fmInit.InvokeExecute(); + + //Assert + Assert.AreEqual(sendProfilerEvents, m_Addressables.ResourceManager.postProfilerEvents); + + //Cleanup + ProjectConfigData.PostProfilerEvents = originalValue; + } + + [TestCase(true)] + [TestCase(false)] + public void FastModeInit_EventViewerSetup_InitializesDiagnosticEventCollectorCorrectly(bool sendProfilerEvents) + { + //Setup + bool originalValue = ProjectConfigData.PostProfilerEvents; + ProjectConfigData.PostProfilerEvents = sendProfilerEvents; + var settings = AddressableAssetSettings.Create(Path.Combine(GetGeneratedAssetsPath(), "Settings"), "AddressableAssetSettings.Tests", false, true); + + //Test + FastModeInitializationOperation fmInit = new FastModeInitializationOperation(m_Addressables, settings); + fmInit.InvokeExecute(); + + //Assert + if(sendProfilerEvents) + Assert.IsNotNull(fmInit.m_Diagnostics, "Diagnostic event collector was null when send profiler events was set to true."); + else + Assert.IsNull(fmInit.m_Diagnostics, "Diagnostic event collector was not null when send profiler events was false."); + + //Cleanup + ProjectConfigData.PostProfilerEvents = originalValue; + } + } +#endif +} diff --git a/Tests/Runtime/Initialization/FastModeInitializationTests.cs.meta b/Tests/Runtime/Initialization/FastModeInitializationTests.cs.meta new file mode 100644 index 00000000..75d2c134 --- /dev/null +++ b/Tests/Runtime/Initialization/FastModeInitializationTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 441d6ab2de7dd3948b7b50882e4370e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/InitializationObjectsAsyncTests.cs b/Tests/Runtime/InitializationObjectsAsyncTests.cs index d1554183..dcf58be0 100644 --- a/Tests/Runtime/InitializationObjectsAsyncTests.cs +++ b/Tests/Runtime/InitializationObjectsAsyncTests.cs @@ -13,7 +13,6 @@ public abstract class InitializationObjectsAsyncTests : AddressablesTestFixture { [UnityTest] - [Timeout(3000)] public IEnumerator InitializationObjects_CompletesWhenNoObjectsPresent() { if (m_RuntimeSettingsPath.StartsWith("GUID:")) @@ -37,7 +36,6 @@ public IEnumerator InitializationObjects_CompletesWhenNoObjectsPresent() } [Test] - [Timeout(3000)] public void InitializationObjects_CompletesSyncWhenNoObjectsPresent() { if (m_RuntimeSettingsPath.StartsWith("GUID:")) @@ -87,7 +85,6 @@ public void InitializationObjects_OperationRegistersForCallbacks() #if UNITY_EDITOR [UnityTest] - [Timeout(5000)] public IEnumerator InitializationObjects_CompletesWhenObjectsPresent() { if (m_RuntimeSettingsPath.StartsWith("GUID:")) @@ -117,7 +114,6 @@ public IEnumerator InitializationObjects_CompletesWhenObjectsPresent() } [Test] - [Timeout(5000)] public void InitializationObjects_CompletesSyncWhenObjectsPresent() { if (m_RuntimeSettingsPath.StartsWith("GUID:")) @@ -148,7 +144,6 @@ public void InitializationObjects_CompletesSyncWhenObjectsPresent() #endif [UnityTest] - [Timeout(3000)] public IEnumerator InitializationAsync_HandlesEmptyData() { if (m_RuntimeSettingsPath.StartsWith("GUID:")) diff --git a/Tests/Runtime/ResourceManager/Operations/BaseOperationBehaviorTests.cs b/Tests/Runtime/ResourceManager/Operations/BaseOperationBehaviorTests.cs index 4f5d924c..27e56b5a 100644 --- a/Tests/Runtime/ResourceManager/Operations/BaseOperationBehaviorTests.cs +++ b/Tests/Runtime/ResourceManager/Operations/BaseOperationBehaviorTests.cs @@ -131,6 +131,36 @@ public void WhenOperationIsSuccessfulButHasErrorMsg_FailsSilently_CompletesButEx Assert.AreEqual(AsyncOperationStatus.Succeeded, status); op.Release(); } + + [UnityTest] + public IEnumerator AsyncOperationHandle_TaskIsDelayedUntilAfterDelayedCompletedCallbacks() + { + var op = m_RM.CreateCompletedOperationInternal(1, true, null); + + var status = AsyncOperationStatus.None; + op.Completed += (x) => status = x.Status; + var t = op.Task; + Assert.IsFalse(t.IsCompleted); + + // callbacks are deferred to next update + m_RM.Update(0.0f); + + // the Task may not yet have continues after at this point on the update, + // give the Synchronization a little time with a yield + yield return null; + + Assert.IsTrue(t.IsCompleted); + op.Release(); + } + + [Test] + public void AsyncOperationHandle_TaskIsCompletedWhenHandleIsCompleteWithoutDelayedCallbacks() + { + var op = m_RM.CreateCompletedOperationInternal(1, true, null); + var t = op.Task; + Assert.IsTrue(t.IsCompleted); + op.Release(); + } // TODO: // public void WhenOperationHasDependency_AndDependencyFails_DependentOpStillExecutes() diff --git a/Tests/Runtime/ResourceManager/ResourceManagerBaseTests.cs b/Tests/Runtime/ResourceManager/ResourceManagerBaseTests.cs index 7a89f9f0..0805ce6f 100644 --- a/Tests/Runtime/ResourceManager/ResourceManagerBaseTests.cs +++ b/Tests/Runtime/ResourceManager/ResourceManagerBaseTests.cs @@ -157,9 +157,14 @@ class AsyncAwaitMultipleComponent : MonoBehaviour public GameObject result; public bool done = false; public AsyncOperationHandle operation; + public bool addCompletedCallback; + public bool callbackDone = false; async void Start() { operation = resourceManager.ProvideResource(location); + if (addCompletedCallback) + operation.Completed += handle => { callbackDone = true; }; + await operation.Task; result = operation.Result; await operation.Task; @@ -168,7 +173,8 @@ async void Start() } } - [UnityTest] + [UnityTest, Timeout(10000)] + [Ignore("Test is unstable on Katana. Needs investigation and either fixed or removed. https://jira.unity3d.com/browse/ADDR-1867")] public IEnumerator WhenAsyncOperationIsDone_TaskIsCompleted() { // Setup @@ -188,5 +194,29 @@ public IEnumerator WhenAsyncOperationIsDone_TaskIsCompleted() comp.operation.Release(); GameObject.Destroy(go); } + + [UnityTest, Timeout(10000)] + [Ignore("Test is unstable on Katana. Needs investigation and either fixed or removed. https://jira.unity3d.com/browse/ADDR-1867")] + public IEnumerator WhenAsyncOperationIsDone_TasksAndCallbackIsCompleted() + { + // Setup + var go = new GameObject("test", typeof(AsyncAwaitMultipleComponent)); + var comp = go.GetComponent(); + comp.resourceManager = m_ResourceManager; + comp.location = m_Locations[0]; + comp.addCompletedCallback = true; + + // Test + while (!comp.done) + yield return null; + Assert.IsNotNull(comp.result); + Assert.True(comp.operation.PercentComplete == 1 && comp.operation.IsDone); + Assert.True(comp.operation.Task.IsCompleted, "Task has not completed before component was done"); + Assert.True(comp.callbackDone, "Callback had not completed before component was done"); + + // Cleanup + comp.operation.Release(); + GameObject.Destroy(go); + } } } diff --git a/Tests/Runtime/SceneTests.cs b/Tests/Runtime/SceneTests.cs index 7885eae7..b5772c45 100644 --- a/Tests/Runtime/SceneTests.cs +++ b/Tests/Runtime/SceneTests.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; #if UNITY_EDITOR using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; @@ -25,6 +26,12 @@ abstract class SceneTests : AddressablesTestFixture const int numScenes = 2; protected List sceneKeys; const string prefabKey = "prefabKey"; + + protected internal string GetPrefabKey() + { + return prefabKey; + } + public SceneTests() { sceneKeys = new List(); @@ -294,7 +301,6 @@ public IEnumerator SceneTests_UnloadSceneAsync_CanUnloadBaseHandle() [UnityTest] public IEnumerator SceneTests_UnloadSceneAsync_CanUnloadFromSceneInstance() { - AddressablesImpl impl = new AddressablesImpl(new DefaultAllocationStrategy()); var op = m_Addressables.LoadSceneAsync(sceneKeys[0], LoadSceneMode.Additive); yield return op; @@ -304,6 +310,98 @@ public IEnumerator SceneTests_UnloadSceneAsync_CanUnloadFromSceneInstance() Assert.AreEqual(0, m_Addressables.m_SceneInstances.Count); } + + [UnityTest] + public IEnumerator SceneTests_UnloadSceneAsync_UnloadSceneDecreaseRefOnlyOnce() + { + var op = m_Addressables.LoadSceneAsync(sceneKeys[0], LoadSceneMode.Additive); + yield return op; + Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status); + Assert.AreEqual(sceneKeys[0], SceneManager.GetSceneByName(sceneKeys[0]).name); + + Addressables.ResourceManager.Acquire(op); + yield return UnloadSceneFromHandlerRefCountCheck(op, m_Addressables); + + // Cleanup + Addressables.Release(op); + } + + [UnityTest] + public IEnumerator SceneTests_Release_ReleaseToZeroRefCountUnloadsScene() + { + var op = m_Addressables.LoadSceneAsync(sceneKeys[0], LoadSceneMode.Additive); + yield return op; + Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status); + Assert.AreEqual(sceneKeys[0], SceneManager.GetSceneByName(sceneKeys[0]).name); + + m_Addressables.Release(op); + yield return null; + + Assert.IsFalse(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Assert.IsFalse(op.IsValid()); + } + + [UnityTest] + public IEnumerator SceneTests_Release_ReleaseToRefCountZeroWhileLoadingUnloadsAfterLoadCompletes() + { + // Setup + var op = m_Addressables.LoadSceneAsync(sceneKeys[0], LoadSceneMode.Additive); + + // Test + op.Completed += s => Assert.IsTrue(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + m_Addressables.Release(op); + yield return op; + Assert.IsFalse(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Assert.IsFalse(op.IsValid()); + + // Cleanup + yield return op; + } + + [UnityTest] + public IEnumerator SceneTests_Release_ReleaseNotRefCountZeroWhileLoadingDoesntUnloadAfterLoadCompletes() + { + // Setup + var op = m_Addressables.LoadSceneAsync(sceneKeys[0], LoadSceneMode.Additive); + Addressables.ResourceManager.Acquire(op); + + // Test + op.Completed += s => Assert.IsTrue(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Addressables.Release(op); + yield return op; + Assert.IsTrue(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Assert.IsTrue(op.IsValid()); + + // Cleanup + yield return op; + Assert.IsTrue(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Assert.IsTrue(op.IsValid()); + m_Addressables.Release(op); + + yield return op; + Assert.IsFalse(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Assert.IsFalse(op.IsValid()); + } + + [UnityTest] + public IEnumerator SceneTests_Release_ReleaseNotToZeroRefCountDoesNotUnloadScene() + { + var op = m_Addressables.LoadSceneAsync(sceneKeys[0], LoadSceneMode.Additive); + yield return op; + Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status); + Assert.AreEqual(sceneKeys[0], SceneManager.GetSceneByName(sceneKeys[0]).name); + Addressables.ResourceManager.Acquire(op); + + m_Addressables.Release(op); + yield return null; + + Assert.IsTrue(SceneManager.GetSceneByName(sceneKeys[0]).isLoaded); + Assert.IsTrue(op.IsValid()); + + // Cleanup + m_Addressables.Release(op); + yield return null; + } [UnityTest] public IEnumerator GetDownloadSize_DoesNotThrowInvalidKeyException_ForScene() @@ -340,6 +438,7 @@ public IEnumerator UnloadScene_ChainsBehindLoadOp_IfLoadOpIsRunning_TypedHandle( //Assert Assert.AreEqual(typeof(ChainOperation), unloadHandle.m_InternalOp.GetType(), "Unload a scene while a Load is in progress should have resulted in the unload being chained behind the load op, but wasn't"); + Addressables.Release(unloadHandle); } [UnityTest] @@ -354,6 +453,51 @@ public IEnumerator UnloadScene_ChainsBehindLoadOp_IfLoadOpIsRunning_TypelessHand //Assert Assert.AreEqual(typeof(ChainOperationTypelessDepedency), unloadHandle.m_InternalOp.GetType(), "Unload a scene while a Load is in progress should have resulted in the unload being chained behind the load op, but wasn't"); + Addressables.Release(unloadHandle); + } + + [UnityTest] + public IEnumerator SceneTests_UnloadSceneAsync_UnloadSceneAfterAcquireAndDoNotDestroyOnLoadDoesNotUnloadDependenciesUntilSecondRelease() + { + // Setup scene + int bundleCountBeforeTest = AssetBundle.GetAllLoadedAssetBundles().Count(); + var activeScene = m_Addressables.LoadSceneAsync(sceneKeys[1], LoadSceneMode.Additive); + yield return activeScene; + + Assert.AreEqual(AsyncOperationStatus.Succeeded, activeScene.Status); + Addressables.ResourceManager.Acquire(activeScene); + Assert.AreEqual(activeScene.ReferenceCount, 2); + SceneManager.SetActiveScene(activeScene.Result.Scene); + Assert.AreEqual(sceneKeys[1], SceneManager.GetActiveScene().name); + + // Setup obj + Assert.IsNull(GameObject.Find(GetPrefabKey())); + var instOp = m_Addressables.InstantiateAsync(GetPrefabKey()); + yield return instOp; + + Assert.AreEqual(AsyncOperationStatus.Succeeded, instOp.Status); + Assert.AreEqual(sceneKeys[1], instOp.Result.scene.name); + UnityEngine.Object.DontDestroyOnLoad(instOp.Result); + int bundleCountAfterInstantiate = AssetBundle.GetAllLoadedAssetBundles().Count(); + Assert.Greater(bundleCountAfterInstantiate,bundleCountBeforeTest); + + // Test + yield return UnloadSceneFromHandlerRefCountCheck(activeScene, m_Addressables); + + Assert.NotNull(GameObject.Find(instOp.Result.name)); + Assert.IsFalse(activeScene.Result.Scene.isLoaded); + int bundleCountAfterUnload = AssetBundle.GetAllLoadedAssetBundles().Count(); + Assert.AreEqual(bundleCountAfterInstantiate, bundleCountAfterUnload); + + Addressables.Release(activeScene); + yield return activeScene; + + // Cleanup + Assert.IsFalse(activeScene.IsValid()); + Addressables.Release(instOp); + int bundleCountEndTest = AssetBundle.GetAllLoadedAssetBundles().Count(); + Assert.AreEqual(bundleCountBeforeTest,bundleCountEndTest); + Assert.IsFalse(instOp.IsValid()); } } #endif diff --git a/package.json b/package.json index 04fc1d4e..ebe5ccc1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.addressables", "displayName": "Addressables", - "version": "1.17.6-preview", + "version": "1.17.13", "unity": "2018.4", "description": "The Addressable Asset System allows the developer to ask for an asset via its address. Once an asset (e.g. a prefab) is marked \"addressable\", it generates an address which can be called from anywhere. Wherever the asset resides (local or remote), the system will locate it and its dependencies, then return it.\n\nUse 'Window->Asset Management->Addressables' to begin working with the system.\n\nAddressables use asynchronous loading to support loading from any location with any collection of dependencies. Whether you have been using direct references, traditional asset bundles, or Resource folders, addressables provide a simpler way to make your game more dynamic. Addressables simultaneously opens up the world of asset bundles while managing all the complexity.\n\nFor usage samples, see github.com/Unity-Technologies/Addressables-Sample", "keywords": [ @@ -12,7 +12,7 @@ "assetbundles" ], "dependencies": { - "com.unity.scriptablebuildpipeline": "1.16.1", + "com.unity.scriptablebuildpipeline": "1.17.0", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", @@ -22,9 +22,9 @@ "repository": { "url": "https://github.cds.internal.unity3d.com/unity/Addressables.git", "type": "git", - "revision": "fa4cd10490da61cfec184f2459296d15c58cf700" + "revision": "cb0ee07235216ca0a1a1acd97c28c75395545582" }, "upmCi": { - "footprint": "858dec334c0249f672ea89cac43e598beade3c01" + "footprint": "609bd5ae506a26b71aa65cc109d3da4d53aa40fe" } }