From 5b0c8c3057f880458ce4b5a731c81dba12c665fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernest=20Sury=C5=9B?= Date: Fri, 11 Aug 2023 08:55:56 +0200 Subject: [PATCH] OneAssetLoader API update --- .../com.quickeye.utility/OneAsset/README.md | 6 +- .../OneAsset/Runtime/AssetLoadOptions.cs | 14 +- .../Runtime/LoadFromAssetAttribute.cs | 11 +- .../OneAsset/Runtime/OneAssetLoader.cs | 135 ++++++------------ .../OneAsset/Runtime/OneGameObject.cs | 18 ++- .../OneAsset/Runtime/OneScriptableObject.cs | 23 ++- .../Editor/AutomaticAssetCreationTests.cs | 8 +- ...omaticAssetCreationWithInheritanceTests.cs | 2 +- .../Tests/Editor/MultipleLoadPathsTests.cs | 6 +- .../Tests/Editor/OneAssetLoaderTests.cs | 12 +- .../Tests/Editor/UnsafeLoadingTests.cs | 2 +- 11 files changed, 107 insertions(+), 130 deletions(-) diff --git a/Packages/com.quickeye.utility/OneAsset/README.md b/Packages/com.quickeye.utility/OneAsset/README.md index 9e96d47..35af909 100644 --- a/Packages/com.quickeye.utility/OneAsset/README.md +++ b/Packages/com.quickeye.utility/OneAsset/README.md @@ -4,7 +4,7 @@ A set of classes and editor UI improvements aimed to improve workflows that require asset loading. **Package contains**: -- `OneAssetLoader` Loads or creates objects with options from `AssetLoadOptions` +- `OneAssetLoader` Loads assets with options from `AssetLoadOptions` - Abstract class that can be inherited to get a singleton behaviour - `OneGameObject` - `OneScriptableObject` @@ -34,7 +34,7 @@ var loadOptions = new AssetLoadOptions(loadPaths) CreateAssetIfMissing = true, // If set to true a exception will be thrown when `OneAssetLoader` wont find asset at any of the provided paths - // if set to false a new instance of ScriptableObject will be created + // if set to false the `OneAssetLoader` will return a null AssetIsMandatory = true, // Use the `UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget` as a fallback load option. @@ -42,7 +42,7 @@ var loadOptions = new AssetLoadOptions(loadPaths) // Works only in editor LoadAndForget = true }; -var asset = OneAssetLoader.LoadOrCreateScriptableObject(loadOptions); +var asset = OneAssetLoader.Load(loadOptions); ``` ## UnityEngine.Object and a Singleton pattern (Disclaimer) diff --git a/Packages/com.quickeye.utility/OneAsset/Runtime/AssetLoadOptions.cs b/Packages/com.quickeye.utility/OneAsset/Runtime/AssetLoadOptions.cs index c89c479..896fe28 100644 --- a/Packages/com.quickeye.utility/OneAsset/Runtime/AssetLoadOptions.cs +++ b/Packages/com.quickeye.utility/OneAsset/Runtime/AssetLoadOptions.cs @@ -31,19 +31,19 @@ public class AssetLoadOptions public bool LoadAndForget { get; set; } /// - /// Path from which will try to load an asset. - /// If path starts with "Resources/" it will be loaded from resources and be available in runtime. - /// File extension is required. + /// Path from which will try to load an asset. + /// If path is absolute and contains a file extension, it will work with all of the options. + /// If path starts or contains the "/Resources/" it will be loaded using and be available in runtime. /// public AssetLoadOptions(string path) : this(new[] { path }) { } /// - /// Paths from which will try to load an asset. - /// Asset will be loaded from the first path that contains loadable asset. - /// If path starts with "Resources/" it will be loaded from resources and be available in runtime. - /// File extension is required. + /// Paths from which will try to load an asset. + /// Asset will be loaded from the first path that contains loadable asset. + /// If path is absolute and contains a file extension, it will work with all of the options. + /// If path starts or contains the "/Resources/" it will be loaded using and be available in runtime. /// public AssetLoadOptions(IEnumerable paths) { diff --git a/Packages/com.quickeye.utility/OneAsset/Runtime/LoadFromAssetAttribute.cs b/Packages/com.quickeye.utility/OneAsset/Runtime/LoadFromAssetAttribute.cs index d76ae5d..e46cb78 100644 --- a/Packages/com.quickeye.utility/OneAsset/Runtime/LoadFromAssetAttribute.cs +++ b/Packages/com.quickeye.utility/OneAsset/Runtime/LoadFromAssetAttribute.cs @@ -39,16 +39,13 @@ public sealed class LoadFromAssetAttribute : Attribute public bool LoadAndForget { get; set; } /// - /// Defines a path at which asset can be found for and . - /// Valid on types derived from or + /// Defines a path at which asset can be found for + /// Valid on types like or /// /// - /// Path at which asset should be found. Should be relative to unity project directory and contain file extensions. - /// Under certain conditions path can be less specific. + /// Path from which will try to load an asset. /// If path is absolute and contains a file extension, it will work with all of the options. - /// If is enabled, the path must be absolute - /// If path - /// Doesn't have to contain file name if is set to true. + /// If path starts or contains the "/Resources/" it will be loaded using and be available in runtime. /// public LoadFromAssetAttribute(string path) { diff --git a/Packages/com.quickeye.utility/OneAsset/Runtime/OneAssetLoader.cs b/Packages/com.quickeye.utility/OneAsset/Runtime/OneAssetLoader.cs index ee31df1..dd0743d 100644 --- a/Packages/com.quickeye.utility/OneAsset/Runtime/OneAssetLoader.cs +++ b/Packages/com.quickeye.utility/OneAsset/Runtime/OneAssetLoader.cs @@ -6,137 +6,84 @@ namespace OneAsset { using static AssetLoadOptionsUtility; /// - /// Load or create ScriptableObjects and Prefabs + /// Loads assets with /// public static partial class OneAssetLoader { + //TODO: make it public all pass user data in AssetLoadOptions + // Update the TryCreateAsset delegate to return bool + // this way user can add custom asset creation logic internal static TryCreateAsset CreateAssetAction; - #region LoadOrCreateScriptableObject - /// - /// Load or create an instance of T with load options + /// Loads an asset with load options /// - /// type of instance - /// Options defining the behaviour or load operation - /// New instance of T or asset instance + /// Options defining the behaviour of load operation + /// Type of the asset + /// Asset instance or null if asset is missing /// Thrown when is enabled and no asset was found at provided paths - public static ScriptableObject LoadOrCreateScriptableObject(Type scriptableObjectType, AssetLoadOptions options) + /// Thrown only in editor, when is enabled and asset creation failed + public static Object Load(AssetLoadOptions options, Type assetType) { if (options == null || options.Paths.Length == 0) - return CreateSo(scriptableObjectType); + return null; // Try to load asset - if (TryLoad(scriptableObjectType, options, out var asset)) - return asset as ScriptableObject; + if (TryLoad(assetType, options, out var asset)) + return asset; // Try to create asset at path if (options.CreateAssetIfMissing && - TryCreateAsset(scriptableObjectType, options) && - TryLoad(scriptableObjectType, options, out asset)) - return asset as ScriptableObject; + typeof(ScriptableObject).IsAssignableFrom(assetType) && + TryCreateAsset(assetType, options) && + TryLoad(assetType, options, out asset)) + return asset; // Throw if asset is mandatory if (options.AssetIsMandatory) - throw new AssetIsMissingException(scriptableObjectType, options.Paths[0]); + throw new AssetIsMissingException(assetType, options.Paths[0]); - // Create and return a new instance - return CreateSo(scriptableObjectType); + return null; } /// - /// Load or create an instance of T with load options + /// Loads an asset with load options /// - /// type - /// New instance of T or asset instance + /// Type of the asset + /// Options defining the behaviour of load operation + /// Asset instance or null if asset is missing /// Thrown when is enabled and no asset was found at provided paths - /// Thrown only in editor, when is enabled and there was an issue with - public static T LoadOrCreateScriptableObject(AssetLoadOptions options) where T : ScriptableObject + /// Thrown only in editor, when is enabled and asset creation failed + public static T Load(AssetLoadOptions options) where T : Object { - return LoadOrCreateScriptableObject(typeof(T), options) as T; + return Load(options, typeof(T)) as T; } /// - /// Load or create an instance of ScriptableObject - /// The will be created based on of ScriptableObject type + /// Loads an asset with load options + /// The will be created based on the from asset type /// - /// New instance of ScriptableObject or asset instance + /// Asset instance or null if asset is missing /// Thrown when is enabled and no asset was found at provided paths - /// Thrown only in editor, when is enabled and there was an issue with - public static ScriptableObject LoadOrCreateScriptableObject(Type scriptableObjectType) + /// Thrown only in editor, when is enabled and asset creation failed + public static Object Load(Type assetType) { - return LoadOrCreateScriptableObject(scriptableObjectType, GetLoadOptions(scriptableObjectType)); + return Load(GetLoadOptions(assetType), assetType); } /// - /// Load or create an instance of T - /// The will be created based on of T + /// Loads an asset with load options + /// The will be created based on the from asset type /// - /// type - /// New instance of T or asset instance + /// Type of the asset + /// Asset instance or null if asset is missing /// Thrown when is enabled and no asset was found at provided paths - /// Thrown only in editor, when is enabled and there was an issue with - public static T LoadOrCreateScriptableObject() where T : ScriptableObject - { - return LoadOrCreateScriptableObject(typeof(T)) as T; - } - - #endregion - - #region LoadOrCreateGameObject - - public static Component LoadOrCreateGameObject(Type componentType, AssetLoadOptions options) + /// Thrown only in editor, when is enabled and asset creation failed + public static T Load() where T : Object { - if (options == null || options.Paths.Length == 0) - return CreateGameObject(componentType); - - // Try to load prefab - if (TryLoad(componentType, options, out var prefab)) - { - var component = (Component)Object.Instantiate(prefab); - component.name = componentType.Name; - return component; - } - - // Throw if asset is mandatory - if (options.AssetIsMandatory) - throw new AssetIsMissingException(componentType, options.Paths[0]); - - - return CreateGameObject(componentType); + return Load(typeof(T)) as T; } - public static T LoadOrCreateGameObject(AssetLoadOptions options) where T : Component - { - return LoadOrCreateGameObject(typeof(T), options) as T; - } - - public static Component LoadOrCreateGameObject(Type componentType) - { - var options = GetLoadOptions(componentType); - return LoadOrCreateGameObject(componentType, options); - } - - public static T LoadOrCreateGameObject() where T : Component - { - return LoadOrCreateGameObject(typeof(T), GetLoadOptions(typeof(T))) as T; - } - - #endregion - - private static Component CreateGameObject(Type componentType) - { - var obj = new GameObject { name = componentType.Name }; - return obj.AddComponent(componentType); - } - - private static ScriptableObject CreateSo(Type scriptableObjectType) - { - var obj = ScriptableObject.CreateInstance(scriptableObjectType); - obj.name = scriptableObjectType.Name; - return obj; - } - private static bool TryCreateAsset(Type type, AssetLoadOptions options) { if (!Application.isEditor || CreateAssetAction == null) @@ -158,7 +105,7 @@ private static bool TryLoad(Type type, AssetLoadOptions options, out Object obj) { foreach (var path in options.AssetPaths) { - if (TryLoadFromResources(type, path, options, out obj)) + if (TryLoadFromResources(type, path, out obj)) return true; if (TryLoadFromAssetDatabase(type, path, out obj)) @@ -172,7 +119,7 @@ private static bool TryLoad(Type type, AssetLoadOptions options, out Object obj) return false; } - private static bool TryLoadFromResources(Type type, AssetPath path, AssetLoadOptions options, + private static bool TryLoadFromResources(Type type, AssetPath path, out Object obj) { if (path.IsInResourcesFolder) diff --git a/Packages/com.quickeye.utility/OneAsset/Runtime/OneGameObject.cs b/Packages/com.quickeye.utility/OneAsset/Runtime/OneGameObject.cs index 43abe24..f874bfe 100644 --- a/Packages/com.quickeye.utility/OneAsset/Runtime/OneGameObject.cs +++ b/Packages/com.quickeye.utility/OneAsset/Runtime/OneGameObject.cs @@ -67,10 +67,26 @@ private static T GetInstance() if (IsAppQuitting) return null; if (_instance == null) - _instance = OneAssetLoader.LoadOrCreateGameObject(); + _instance = LoadOrCreate(); return _instance; } + + private static T LoadOrCreate() + { + var prefab = OneAssetLoader.Load(); + if (prefab == null) + return CreateGameObject(); + var component = Instantiate(prefab); + component.name = typeof(T).Name; + return component; + } + + private static T CreateGameObject() + { + var obj = new GameObject { name = typeof(T).Name }; + return obj.AddComponent(); + } } /// diff --git a/Packages/com.quickeye.utility/OneAsset/Runtime/OneScriptableObject.cs b/Packages/com.quickeye.utility/OneAsset/Runtime/OneScriptableObject.cs index ac9d9f0..3855cf3 100644 --- a/Packages/com.quickeye.utility/OneAsset/Runtime/OneScriptableObject.cs +++ b/Packages/com.quickeye.utility/OneAsset/Runtime/OneScriptableObject.cs @@ -5,7 +5,7 @@ namespace OneAsset /// /// Adds singleton behaviour to descendant classes. /// Loads or creates instance of T when Instance property is used. - /// Can be combined with , and + /// Can be combined with and /// /// Type of the singleton instance public abstract class OneScriptableObject : OneScriptableObject @@ -15,16 +15,33 @@ public abstract class OneScriptableObject : OneScriptableObject /// /// Returns a instance of T. - /// If no instance of T exists, it will create a new one using + /// If no instance of T exists, it will create a new one or load one using /// public static T Instance => GetInstance(); private static T GetInstance() { if (_instance == null) - _instance = OneAssetLoader.LoadOrCreateScriptableObject(); + _instance = LoadOrCreate(); return _instance; } + + private static T LoadOrCreate() + { + var asset = OneAssetLoader.Load(); + if (asset == null) + return CreateScriptableObject(); + var component = Instantiate(asset); + component.name = typeof(T).Name; + return component; + } + + private static T CreateScriptableObject() + { + var obj = CreateInstance(); + obj.name = typeof(T).Name; + return obj; + } } diff --git a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationTests.cs b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationTests.cs index 61452e8..d12dd94 100644 --- a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationTests.cs +++ b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationTests.cs @@ -30,7 +30,7 @@ public void OneTimeTearDown() [Test] public void Should_CreateNewAsset_When_TypeHasCreateAutomaticallyAttributeAndAssetIsMissing() { - var asset = OneAssetLoader.LoadOrCreateScriptableObject(); + var asset = OneAssetLoader.Load(); var assetPath = AssetDatabase.GetAssetPath(asset); StringAssert.Contains(SoWithCreateAutomatically.AbsoluteAssetPath, assetPath); @@ -39,7 +39,7 @@ public void Should_CreateNewAsset_When_TypeHasCreateAutomaticallyAttributeAndAss [Test] public void Should_CreateNewAsset_When_AtPathFromTheAttributeWithHighestPriority() { - var asset = OneAssetLoader.LoadOrCreateScriptableObject(); + var asset = OneAssetLoader.Load(); var assetPath = AssetDatabase.GetAssetPath(asset); StringAssert.Contains(SoWithCreateAutomatically2.AbsoluteAssetPathNoExt, assetPath); @@ -54,7 +54,7 @@ public void Should_CreateNewAsset_When_PathHasNoFileExtension() CreateAssetIfMissing = true }; - var asset = OneAssetLoader.LoadOrCreateScriptableObject(typeof(SoWithAsset), options); + var asset = OneAssetLoader.Load(options, typeof(SoWithAsset)); Assert.IsTrue(AssetDatabase.Contains(asset)); var assetPath = AssetDatabase.GetAssetPath(asset); @@ -68,7 +68,7 @@ public void Should_CreateNewAsset_When_PathHasFileExtension() { CreateAssetIfMissing = true }; - var asset = OneAssetLoader.LoadOrCreateScriptableObject(typeof(SoWithAsset), options); + var asset = OneAssetLoader.Load(options, typeof(SoWithAsset)); Assert.IsTrue(AssetDatabase.Contains(asset)); var assetPath = AssetDatabase.GetAssetPath(asset); diff --git a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationWithInheritanceTests.cs b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationWithInheritanceTests.cs index 1e0ccf6..feac0a3 100644 --- a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationWithInheritanceTests.cs +++ b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/AutomaticAssetCreationWithInheritanceTests.cs @@ -29,7 +29,7 @@ public void OneTimeTearDown() //[Test][Ignore("Feature not supported in this version")] public void Should_CreateNewAsset_When_TypeHasInheritedCreateAutomaticallyAttributeAndAssetIsMissing() { - var asset = OneAssetLoader.LoadOrCreateScriptableObject(); + var asset = OneAssetLoader.Load(); Assert.IsTrue(AssetDatabase.Contains(asset)); } diff --git a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/MultipleLoadPathsTests.cs b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/MultipleLoadPathsTests.cs index 750c9ec..2eb707c 100644 --- a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/MultipleLoadPathsTests.cs +++ b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/MultipleLoadPathsTests.cs @@ -13,7 +13,7 @@ public void Should_LoadAssetInstanceWithHighestPriorityPath_When_TypeHasMultiple var asset = Resources.Load(SoWithMultipleLoadPaths1.FirstResourcesPath); Assert.NotNull(asset); - var actual = OneAssetLoader.LoadOrCreateScriptableObject(); + var actual = OneAssetLoader.Load(); Assert.AreEqual(asset, actual); } @@ -26,7 +26,7 @@ public void Should_LoadAssetInstanceWithHighestPriorityPath_When_TypeHasMultiple using (var expectedAssetScope = new TestAssetScope(options.Paths[0])) using (new TestAssetScope(options.Paths[1])) { - var asset = OneAssetLoader.LoadOrCreateScriptableObject(typeof(SoWithAsset),options); + var asset = OneAssetLoader.Load(options, typeof(SoWithAsset)); Assert.NotNull(asset); Assert.AreEqual(expectedAssetScope.Asset, asset); @@ -44,7 +44,7 @@ public void Should_LoadAssetFromFirstPathThatHasIt_When_AssetIsMissingFromFirstP Assert.IsNull(assetFromFirstPath); Assert.NotNull(assetFromSecondaryPath); - var actual = OneAssetLoader.LoadOrCreateScriptableObject(); + var actual = OneAssetLoader.Load(); Assert.AreEqual(assetFromSecondaryPath, actual); } diff --git a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/OneAssetLoaderTests.cs b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/OneAssetLoaderTests.cs index b31fd62..aa727c2 100644 --- a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/OneAssetLoaderTests.cs +++ b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/OneAssetLoaderTests.cs @@ -40,7 +40,7 @@ public void Should_LoadAsset_When_AssetExistsInResources(string path) var options = new AssetLoadOptions(path); using (new TestAssetScope(options.Paths[0])) { - var actual = OneAssetLoader.LoadOrCreateScriptableObject(typeof(SoWithAsset), options); + var actual = OneAssetLoader.Load(options, typeof(SoWithAsset)); Assert.NotNull(actual); Assert.IsTrue(AssetDatabase.Contains(actual)); @@ -55,7 +55,7 @@ public void Should_LoadAsset_When_AssetInNotInResources(string path) var options = new AssetLoadOptions(path); using (new TestAssetScope(options.Paths[0])) { - var actual = OneAssetLoader.LoadOrCreateScriptableObject(typeof(SoWithAsset), options); + var actual = OneAssetLoader.Load(options, typeof(SoWithAsset)); Assert.NotNull(actual); Assert.IsTrue(AssetDatabase.Contains(actual)); @@ -70,16 +70,16 @@ public void Should_Throw_When_TypeHasMandatoryAssetButAssetIsMissing() { Assert.Throws(() => { - OneAssetLoader.LoadOrCreateScriptableObject(); + OneAssetLoader.Load(); }); } [Test] - public void Should_CreateNewInstance_When_TypeHasNonMandatoryAssetAndAssetIsMissing() + public void Should_ReturnNull_When_TypeHasNonMandatoryAssetAndAssetIsMissing() { - var result = OneAssetLoader.LoadOrCreateScriptableObject(); + var result = OneAssetLoader.Load(); - Assert.NotNull(result); + Assert.Null(result); } } } \ No newline at end of file diff --git a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/UnsafeLoadingTests.cs b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/UnsafeLoadingTests.cs index 4507ee4..1ea3543 100644 --- a/Packages/com.quickeye.utility/OneAsset/Tests/Editor/UnsafeLoadingTests.cs +++ b/Packages/com.quickeye.utility/OneAsset/Tests/Editor/UnsafeLoadingTests.cs @@ -32,7 +32,7 @@ public void LoadUnsafe() LoadAndForget = true }; - var instance = OneAssetLoader.LoadOrCreateScriptableObject(typeof(SoWithAsset), options); + var instance = OneAssetLoader.Load(options, typeof(SoWithAsset)); Assert.NotNull(instance); Assert.AreEqual(typeof(SoWithAsset), instance.GetType());