Skip to content

Commit

Permalink
OneAssetLoader API update
Browse files Browse the repository at this point in the history
  • Loading branch information
ErnSur committed Aug 11, 2023
1 parent 5fd6c9a commit 5b0c8c3
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 130 deletions.
6 changes: 3 additions & 3 deletions Packages/com.quickeye.utility/OneAsset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`
- `OneScriptableObject<T>`
Expand Down Expand Up @@ -34,15 +34,15 @@ 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.
// Use with caution!
// Works only in editor
LoadAndForget = true
};
var asset = OneAssetLoader.LoadOrCreateScriptableObject<SampleScriptableObject>(loadOptions);
var asset = OneAssetLoader.Load<SampleScriptableObject>(loadOptions);
```

## UnityEngine.Object and a Singleton pattern (Disclaimer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ public class AssetLoadOptions
public bool LoadAndForget { get; set; }

/// <param name="path">
/// Path from which <see cref="OneAssetLoader"/> 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.
/// <para>Path from which <see cref="OneAssetLoader"/> will try to load an asset.</para>
/// <para>If path is absolute and contains a file extension, it will work with all of the options.</para>
/// <para>If path starts or contains the "/Resources/" it will be loaded using <see cref="UnityEngine.Resources"/> and be available in runtime.</para>
/// </param>
public AssetLoadOptions(string path) : this(new[] { path })
{
}

/// <param name="paths">
/// Paths from which <see cref="OneAssetLoader"/> 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.
/// <para>Paths from which <see cref="OneAssetLoader"/> will try to load an asset.</para>
/// <para>Asset will be loaded from the first path that contains loadable asset.</para>
/// <para>If path is absolute and contains a file extension, it will work with all of the options.</para>
/// <para>If path starts or contains the "/Resources/" it will be loaded using <see cref="UnityEngine.Resources"/> and be available in runtime.</para>
/// </param>
public AssetLoadOptions(IEnumerable<string> paths)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,13 @@ public sealed class LoadFromAssetAttribute : Attribute
public bool LoadAndForget { get; set; }

/// <summary>
/// Defines a path at which asset can be found for <see cref="OneAssetLoader"/> and <see cref="OneGameObject{T}"/>.
/// Valid on types derived from <see cref="UnityEngine.ScriptableObject"/> or <see cref="OneGameObject{T}"/>
/// Defines a path at which asset can be found for <see cref="OneAssetLoader"/>
/// Valid on types like <see cref="UnityEngine.ScriptableObject"/> or <see cref="UnityEngine.MonoBehaviour"/>
/// </summary>
/// <param name="path">
/// 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.
/// <para>Path from which <see cref="OneAssetLoader"/> will try to load an asset.</para>
/// <para>If path is absolute and contains a file extension, it will work with all of the options.</para>
/// <para>If <see cref="CreateAssetIfMissing"/> is enabled, the path must be absolute</para>
/// <para>If path </para>
/// Doesn't have to contain file name if <see cref="UseTypeNameAsFileName"/> is set to true.
/// <para>If path starts or contains the "/Resources/" it will be loaded using <see cref="UnityEngine.Resources"/> and be available in runtime.</para>
/// </param>
public LoadFromAssetAttribute(string path)
{
Expand Down
135 changes: 41 additions & 94 deletions Packages/com.quickeye.utility/OneAsset/Runtime/OneAssetLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,137 +6,84 @@ namespace OneAsset
{
using static AssetLoadOptionsUtility;
/// <summary>
/// <para>Load or create ScriptableObjects and Prefabs</para>
/// <para>Loads assets with <see cref="AssetLoadOptions"/></para>
/// </summary>
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

/// <summary>
/// <para>Load or create an instance of T with load options</para>
/// <para>Loads an asset with load options</para>
/// </summary>
/// <param name="scriptableObjectType"><see cref="UnityEngine.ScriptableObject"/> type of instance</param>
/// <param name="options">Options defining the behaviour or load operation</param>
/// <returns>New instance of T or asset instance</returns>
/// <param name="options">Options defining the behaviour of load operation</param>
/// <param name="assetType">Type of the asset</param>
/// <returns>Asset instance or null if asset is missing</returns>
/// <exception cref="AssetIsMissingException">Thrown when <see cref="AssetLoadOptions.AssetIsMandatory"/> is enabled and no asset was found at provided paths</exception>
public static ScriptableObject LoadOrCreateScriptableObject(Type scriptableObjectType, AssetLoadOptions options)
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and asset creation failed</exception>
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;
}

/// <summary>
/// <para>Load or create an instance of T with load options</para>
/// <para>Loads an asset with load options</para>
/// </summary>
/// <typeparam name="T"><see cref="ScriptableObject"/> type</typeparam>
/// <returns>New instance of T or asset instance</returns>
/// <typeparam name="T">Type of the asset</typeparam>
/// <param name="options">Options defining the behaviour of load operation</param>
/// <returns>Asset instance or null if asset is missing</returns>
/// <exception cref="AssetIsMissingException">Thrown when <see cref="AssetLoadOptions.AssetIsMandatory"/> is enabled and no asset was found at provided paths</exception>
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and there was an issue with <see cref="UnityEditor.AssetDatabase"/></exception>
public static T LoadOrCreateScriptableObject<T>(AssetLoadOptions options) where T : ScriptableObject
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and asset creation failed</exception>
public static T Load<T>(AssetLoadOptions options) where T : Object
{
return LoadOrCreateScriptableObject(typeof(T), options) as T;
return Load(options, typeof(T)) as T;
}

/// <summary>
/// <para>Load or create an instance of ScriptableObject</para>
/// <para>The <see cref="AssetLoadOptions"/> will be created based on <see cref="LoadFromAssetAttribute"/> of ScriptableObject type</para>
/// <para>Loads an asset with load options</para>
/// <para>The <see cref="AssetLoadOptions"/> will be created based on the <see cref="LoadFromAssetAttribute"/> from asset type</para>
/// </summary>
/// <returns>New instance of ScriptableObject or asset instance</returns>
/// <returns>Asset instance or null if asset is missing</returns>
/// <exception cref="AssetIsMissingException">Thrown when <see cref="AssetLoadOptions.AssetIsMandatory"/> is enabled and no asset was found at provided paths</exception>
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and there was an issue with <see cref="UnityEditor.AssetDatabase"/></exception>
public static ScriptableObject LoadOrCreateScriptableObject(Type scriptableObjectType)
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and asset creation failed</exception>
public static Object Load(Type assetType)
{
return LoadOrCreateScriptableObject(scriptableObjectType, GetLoadOptions(scriptableObjectType));
return Load(GetLoadOptions(assetType), assetType);
}

/// <summary>
/// <para>Load or create an instance of T</para>
/// <para>The <see cref="AssetLoadOptions"/> will be created based on <see cref="LoadFromAssetAttribute"/> of T</para>
/// <para>Loads an asset with load options</para>
/// <para>The <see cref="AssetLoadOptions"/> will be created based on the <see cref="LoadFromAssetAttribute"/> from asset type</para>
/// </summary>
/// <typeparam name="T"><see cref="ScriptableObject"/> type</typeparam>
/// <returns>New instance of T or asset instance</returns>
/// <typeparam name="T">Type of the asset</typeparam>
/// <returns>Asset instance or null if asset is missing</returns>
/// <exception cref="AssetIsMissingException">Thrown when <see cref="AssetLoadOptions.AssetIsMandatory"/> is enabled and no asset was found at provided paths</exception>
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and there was an issue with <see cref="UnityEditor.AssetDatabase"/></exception>
public static T LoadOrCreateScriptableObject<T>() where T : ScriptableObject
{
return LoadOrCreateScriptableObject(typeof(T)) as T;
}

#endregion

#region LoadOrCreateGameObject

public static Component LoadOrCreateGameObject(Type componentType, AssetLoadOptions options)
/// <exception cref="EditorAssetFactoryException">Thrown only in editor, when <see cref="AssetLoadOptions.CreateAssetIfMissing"/> is enabled and asset creation failed</exception>
public static T Load<T>() 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<T>(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<T>() 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)
Expand All @@ -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))
Expand All @@ -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)
Expand Down
18 changes: 17 additions & 1 deletion Packages/com.quickeye.utility/OneAsset/Runtime/OneGameObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,26 @@ private static T GetInstance()
if (IsAppQuitting)
return null;
if (_instance == null)
_instance = OneAssetLoader.LoadOrCreateGameObject<T>();
_instance = LoadOrCreate();

return _instance;
}

private static T LoadOrCreate()
{
var prefab = OneAssetLoader.Load<T>();
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<T>();
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace OneAsset
/// <summary>
/// Adds singleton behaviour to descendant classes.
/// Loads or creates instance of T when Instance property is used.
/// Can be combined with <see cref="LoadFromAssetAttribute"/>, <see cref="CreateAssetAutomaticallyAttribute"/> and <see cref="SettingsProviderAssetAttribute"/>
/// Can be combined with <see cref="LoadFromAssetAttribute"/> and <see cref="SettingsProviderAssetAttribute"/>
/// </summary>
/// <typeparam name="T">Type of the singleton instance</typeparam>
public abstract class OneScriptableObject<T> : OneScriptableObject
Expand All @@ -15,16 +15,33 @@ public abstract class OneScriptableObject<T> : OneScriptableObject

/// <summary>
/// Returns a instance of T.
/// If no instance of T exists, it will create a new one using <see cref="OneAssetLoader.LoadOrCreateInstance{T}"/>
/// If no instance of T exists, it will create a new one or load one using <see cref="OneAssetLoader.Load{T}()"/>
/// </summary>
public static T Instance => GetInstance();

private static T GetInstance()
{
if (_instance == null)
_instance = OneAssetLoader.LoadOrCreateScriptableObject<T>();
_instance = LoadOrCreate();
return _instance;
}

private static T LoadOrCreate()
{
var asset = OneAssetLoader.Load<T>();
if (asset == null)
return CreateScriptableObject();
var component = Instantiate(asset);
component.name = typeof(T).Name;
return component;
}

private static T CreateScriptableObject()
{
var obj = CreateInstance<T>();
obj.name = typeof(T).Name;
return obj;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void OneTimeTearDown()
[Test]
public void Should_CreateNewAsset_When_TypeHasCreateAutomaticallyAttributeAndAssetIsMissing()
{
var asset = OneAssetLoader.LoadOrCreateScriptableObject<SoWithCreateAutomatically>();
var asset = OneAssetLoader.Load<SoWithCreateAutomatically>();

var assetPath = AssetDatabase.GetAssetPath(asset);
StringAssert.Contains(SoWithCreateAutomatically.AbsoluteAssetPath, assetPath);
Expand All @@ -39,7 +39,7 @@ public void Should_CreateNewAsset_When_TypeHasCreateAutomaticallyAttributeAndAss
[Test]
public void Should_CreateNewAsset_When_AtPathFromTheAttributeWithHighestPriority()
{
var asset = OneAssetLoader.LoadOrCreateScriptableObject<SoWithCreateAutomatically2>();
var asset = OneAssetLoader.Load<SoWithCreateAutomatically2>();

var assetPath = AssetDatabase.GetAssetPath(asset);
StringAssert.Contains(SoWithCreateAutomatically2.AbsoluteAssetPathNoExt, assetPath);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 5b0c8c3

Please sign in to comment.