diff --git a/.github/workflows/docfx-build-publish.yml b/.github/workflows/docfx-build-publish.yml
index 454393f..78d090b 100644
--- a/.github/workflows/docfx-build-publish.yml
+++ b/.github/workflows/docfx-build-publish.yml
@@ -23,22 +23,15 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v6.0.1
- - name: Setup .NET Core
- uses: actions/setup-dotnet@v1
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5.0.1
with:
- dotnet-version: 3.1.101
+ dotnet-version: '8.0.x'
- name: Setup DocFX
- uses: crazy-max/ghaction-chocolatey@v1
- with:
- args: install docfx --ignore-checksums
-
- - name: Setup wkhtmltopdf
- uses: crazy-max/ghaction-chocolatey@v1
- with:
- args: install wkhtmltopdf --ignore-checksums
+ run: dotnet tool install -g docfx --version 2.61.0
- name: Copy readme
run: cp .\README.md .\Tools~\DocFx\index.md
@@ -50,32 +43,48 @@ jobs:
continue-on-error: false
- name: Upload site artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v6.0.0
with:
name: _site
path: Tools~/DocFx/_site
-
- - name: Upload PDF
- uses: actions/upload-artifact@v2.2.4
- with:
- name: _site_pdf
- path: Tools~/DocFx/_site_pdf/DocFx.pdf
+ deploy_test:
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6.0.1
+
+ - name: Download site artifact
+ uses: actions/download-artifact@v7.0.0
+ with:
+ name: _site
+ path: _site
+
+ - name: Publish
+ uses: peaceiris/actions-gh-pages@v4.0.0
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_branch: gh-pages-test
+ publish_dir: _site
+ force_orphan: true
+
deploy:
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v6.0.1
- name: Download site artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v7.0.0
with:
name: _site
+ path: _site
- name: Publish
- uses: peaceiris/actions-gh-pages@v3
+ uses: peaceiris/actions-gh-pages@v4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb49f05..5df2bad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 2.0.0
+- Add asset release API
+- Add async loading cancellation support
+- Support UniTask for async loading
+- Add editor tool for setting references through editor scripts
+- Update documentation
+
## 1.0.0
- Improve scene reference, add auto fix button
- Use version defines
diff --git a/Editor/SceneReferenceEditor.cs b/Editor/SceneReferenceEditor.cs
index 8cb4ae9..f4b07b6 100644
--- a/Editor/SceneReferenceEditor.cs
+++ b/Editor/SceneReferenceEditor.cs
@@ -2,14 +2,17 @@
using UnityEditor;
using UnityEngine;
-namespace SmartReference.Editor {
+namespace SmartReference.Editor
+{
[CustomPropertyDrawer(typeof(Runtime.SceneReference), true)]
- internal class SceneReferenceEditor: PropertyDrawer {
+ internal class SceneReferenceEditor: PropertyDrawer
+ {
private SerializedProperty cacheProperty;
private SceneAsset currentScene;
private SerializedProperty scenePathProp;
- public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
if (!SerializedProperty.EqualContents(property, cacheProperty)) {
cacheProperty = property;
scenePathProp = property.FindPropertyRelative("scenePath");
@@ -64,7 +67,8 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
}
}
- public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
var height = EditorGUIUtility.singleLineHeight;
var scenePath = property.FindPropertyRelative("scenePath").stringValue;
var buildSettingsScenes = EditorBuildSettings.scenes;
diff --git a/Editor/SmartReference.Editor.asmdef b/Editor/SmartReference.Editor.asmdef
index 9e2de7e..bf80c6a 100644
--- a/Editor/SmartReference.Editor.asmdef
+++ b/Editor/SmartReference.Editor.asmdef
@@ -1,6 +1,6 @@
{
"name": "SmartReference.Editor",
- "rootNamespace": "SmartReference",
+ "rootNamespace": "SmartReference.Editor",
"includePlatforms": [
"Editor"
],
diff --git a/Editor/SmartReferenceEditor.cs b/Editor/SmartReferenceEditor.cs
index 5d4aa0f..f94fea0 100644
--- a/Editor/SmartReferenceEditor.cs
+++ b/Editor/SmartReferenceEditor.cs
@@ -3,9 +3,11 @@
using UnityEngine;
using Object = UnityEngine.Object;
-namespace SmartReference.Editor {
+namespace SmartReference.Editor
+{
[CustomPropertyDrawer(typeof(Runtime.SmartReference), true)]
- internal class SmartReferenceEditor: PropertyDrawer {
+ internal class SmartReferenceEditor: PropertyDrawer
+ {
private SerializedProperty cacheProperty;
private Object referencedObject;
private SerializedProperty guidProp;
@@ -13,7 +15,8 @@ internal class SmartReferenceEditor: PropertyDrawer {
private SerializedProperty pathProp;
private SerializedProperty typeProp;
- public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
if (!SerializedProperty.EqualContents(property, cacheProperty)) {
cacheProperty = property;
referencedObject = null;
@@ -38,26 +41,16 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
}
var type = Type.GetType(typeProp.stringValue);
- var referenced = EditorGUI.ObjectField(position, label, referencedObject, type, false);
- if (referencedObject != referenced) {
- if (referenced == null) {
- guidProp.stringValue = string.Empty;
- fileIDProp.longValue = 0;
- pathProp.stringValue = string.Empty;
- return;
+ var newReferenced = EditorGUI.ObjectField(position, label, referencedObject, type, false);
+ if (referencedObject != newReferenced) {
+ referencedObject = newReferenced;
+ if (newReferenced == null) {
+ SmartReferenceTool.ClearReference(property);
}
-
- if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(referenced, out var guid, out long fileID)) {
- Debug.LogError(
- $"[SmartReferenceEditor] Failed to get guid and fileID, path: {AssetDatabase.GetAssetPath(referenced)}");
- return;
+ else
+ {
+ SmartReferenceTool.SetReference(property, newReferenced);
}
-
- guidProp.stringValue = guid;
- fileIDProp.longValue = fileID;
- pathProp.stringValue = AssetDatabase.GetAssetPath(referenced);
-
- referencedObject = referenced;
}
}
}
diff --git a/Editor/SmartReferencePreBuildProcessor.cs b/Editor/SmartReferencePreBuildProcessor.cs
index 1a0b581..28906ff 100644
--- a/Editor/SmartReferencePreBuildProcessor.cs
+++ b/Editor/SmartReferencePreBuildProcessor.cs
@@ -2,8 +2,10 @@
using UnityEditor.Build.Reporting;
using UnityEngine;
-namespace SmartReference.Editor {
- public class SmartReferencePreBuildProcessor: IPreprocessBuildWithReport {
+namespace SmartReference.Editor
+{
+ public class SmartReferencePreBuildProcessor: IPreprocessBuildWithReport
+ {
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report) {
diff --git a/Editor/SmartReferenceTool.cs b/Editor/SmartReferenceTool.cs
new file mode 100644
index 0000000..a01eda1
--- /dev/null
+++ b/Editor/SmartReferenceTool.cs
@@ -0,0 +1,77 @@
+using System;
+using UnityEditor;
+using Object = UnityEngine.Object;
+
+namespace SmartReference.Editor
+{
+ public static class SmartReferenceTool
+ {
+ public static void SetReference(SerializedProperty smartReferenceProperty, Object obj)
+ {
+ if (smartReferenceProperty == null)
+ {
+ throw new ArgumentNullException(nameof(smartReferenceProperty));
+ }
+
+ if (obj == null)
+ {
+ ClearReference(smartReferenceProperty);
+ return;
+ }
+
+ EnsureIsSmartReferenceProperty(smartReferenceProperty);
+
+ var typeProp = smartReferenceProperty.FindPropertyRelative("type");
+ var type = Type.GetType(typeProp.stringValue);
+ if (obj.GetType() != type)
+ {
+ throw new ArgumentException($"Object type does not match SmartReference type. Expected: {type}, Actual: {obj.GetType()}");
+ }
+
+ if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long fileID))
+ {
+ throw new ArgumentException($"Object is not a valid asset with GUID/fileID: {obj}");
+ }
+
+ var path = AssetDatabase.GetAssetPath(obj);
+ smartReferenceProperty.FindPropertyRelative("guid").stringValue = guid;
+ smartReferenceProperty.FindPropertyRelative("fileID").longValue = fileID;
+ smartReferenceProperty.FindPropertyRelative("path").stringValue = path;
+
+ smartReferenceProperty.serializedObject.ApplyModifiedProperties();
+ }
+
+ ///
+ /// Clears SmartReference fields (guid/fileID/path/type).
+ ///
+ public static void ClearReference(SerializedProperty smartReferenceProperty)
+ {
+ if (smartReferenceProperty == null)
+ throw new ArgumentNullException(nameof(smartReferenceProperty));
+
+ EnsureIsSmartReferenceProperty(smartReferenceProperty);
+
+ smartReferenceProperty.FindPropertyRelative("guid").stringValue = string.Empty;
+ smartReferenceProperty.FindPropertyRelative("fileID").longValue = 0;
+ smartReferenceProperty.FindPropertyRelative("path").stringValue = string.Empty;
+
+ smartReferenceProperty.serializedObject.ApplyModifiedProperties();
+ }
+
+ private static void EnsureIsSmartReferenceProperty(SerializedProperty p)
+ {
+ // We check presence of the expected fields rather than type name, so it works with SmartReference
+ // stored in any container.
+ if (p.propertyType != SerializedPropertyType.Generic)
+ throw new ArgumentException($"Property is not a serialized object/struct: {p.propertyPath}, type: {p.propertyType}");
+
+ if (p.FindPropertyRelative("guid") == null ||
+ p.FindPropertyRelative("fileID") == null ||
+ p.FindPropertyRelative("path") == null ||
+ p.FindPropertyRelative("type") == null)
+ {
+ throw new ArgumentException($"Property does not look like a SmartReference (missing guid/fileID/path/type): {p.propertyPath}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/SmartReferenceTool.cs.meta b/Editor/SmartReferenceTool.cs.meta
new file mode 100644
index 0000000..45f4bf3
--- /dev/null
+++ b/Editor/SmartReferenceTool.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1c250b6bc8094525bc4110b58a3b2e50
+timeCreated: 1766135892
\ No newline at end of file
diff --git a/Editor/SmartReferenceUtils.cs b/Editor/SmartReferenceUtils.cs
index 364148c..9e72bac 100644
--- a/Editor/SmartReferenceUtils.cs
+++ b/Editor/SmartReferenceUtils.cs
@@ -4,13 +4,17 @@
using UnityEditor;
using UnityEngine;
-namespace SmartReference.Editor {
- public static class SmartReferenceUtils {
+namespace SmartReference.Editor
+{
+ public static class SmartReferenceUtils
+ {
[MenuItem("Tools/SmartReference/Update All References", priority = 100)]
- public static void UpdateAllReferences() {
+ public static void UpdateAllReferences()
+ {
EditorUtility.DisplayProgressBar("SmartReference", "Updating all references...", 0);
- try {
+ try
+ {
var typeList = GetTypesWithSpecificField(typeof(Runtime.SmartReference));
foreach (var type in typeList) {
var guids = AssetDatabase.FindAssets($"t:{type.Name}");
@@ -37,7 +41,8 @@ public static void UpdateAllReferences() {
}
}
- public static void UpdateReference(this Runtime.SmartReference smartReference) {
+ public static void UpdateReference(this Runtime.SmartReference smartReference)
+ {
if (smartReference == null || string.IsNullOrEmpty(smartReference.guid)) return;
var succeed = false;
@@ -68,7 +73,8 @@ public static void UpdateReference(this Runtime.SmartReference smartReference) {
}
}
- internal static void UpdateReferenceWithProperty(SerializedProperty property) {
+ internal static void UpdateReferenceWithProperty(SerializedProperty property)
+ {
if (property == null) return;
var guidProp = property.FindPropertyRelative("guid");
@@ -110,7 +116,7 @@ private static List GetTypesWithSpecificField(Type fieldType)
List result = new List();
// Iterating over all assemblies
- foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// Optional: Filter the assemblies by name if necessary
var fullName = assembly.FullName;
@@ -118,7 +124,7 @@ private static List GetTypesWithSpecificField(Type fieldType)
fullName.StartsWith("Unity.") || fullName.StartsWith("Bee.") ||
fullName.StartsWith("System.") || fullName.StartsWith("Mono.")) continue;
- foreach (Type type in assembly.GetTypes())
+ foreach (var type in assembly.GetTypes())
{
// Safeguard against types that might throw exceptions
try
diff --git a/README.md b/README.md
index 69dcc1e..fc68acd 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,92 @@
# Smart Reference
-#### version 1.0.0
+#### version 2.0.0
+
+[](https://github.com/Brian-Jiang/SmartReference/actions/workflows/docfx-build-publish.yml)
+[](https://github.com/Brian-Jiang/SmartReference/releases)
+[](https://openupm.com/packages/com.xiaojiang.smartreference/)
+[](https://u3d.as/35Sh)
+
+---
## Summary
-Smart Reference is a Unity plugin that allows you to lazy load references to other objects in ScriptableObject and MonoBehaviour.
-You may be familiar to use ScriptableObject store data, but when you reference other objects,
-they will be treated as dependencies and will be loaded when the ScriptableObject is loaded. This could be slow if you have a lot of references.
-For details, see [this article](https://medium.com/@bjjx1999/3-ways-to-reduce-load-time-in-runtime-for-unity-15d33003eb79).
-Smart Reference allows you to load references only when you need them at runtime with same workflow in editor.
+Smart Reference is a Unity plugin that enables lazy loading asset references in `ScriptableObject` and `MonoBehaviour`.
+In Unity, referencing assets directly creates implicit dependencies.
+When the `MonoBehaviour` or `ScriptableObject` is loaded, all referenced assets are loaded immediately, which can lead to slow startup times and unnecessary memory usage, especially in data driven projects.
+
+Smart Reference solves this by allowing you to reference assets without loading them upfront, while keeping the same editor workflow.
+Assets are only loaded when you use them at runtime.
## Quick Start
-1. Use SmartReference instead of Object. See how they look exactly same in the inspector.
- ```csharp
- public class MonsterData : ScriptableObject {
- public SmartReference prefab;
- public SmartReference icon;
- public string description;
- }
- ```
- Use SceneReference instead of entering scene name as string.
- See the info tip or auto fix button if the scene is missing or not enabled in build settings.
- ```csharp
- public class GameConfig : ScriptableObject {
- public SceneReference startScene;
- }
- ```
- Use them exactly same way as you use regular Object and scene name.
- ```csharp
- Object.Instantiate(monsterData.prefab, position, rotation);
- SceneManager.LoadSceneAsync(gameConfig.startScene);
- ```
-
-2. Initialize smart reference when your game start
- - If you use Unity Addressables, call this when your game start
- ```csharp
- SmartReference.Runtime.SmartReference.InitWithAddressablesLoader();
- ```
- - If you use your own custom loader, call
- ```csharp
- SmartReference.Runtime.SmartReference.Init((path, type) => {
- return MyLoadFunction.Load(path, type);
- }, (path, type, callback) => {
- MyLoadFunction.LoadAsync(path, type, obj => {
- callback(obj);
- });
- });
- ```
- or
- ```csharp
- var loader = new CustomLoader {
- loader = MyLoadFunction,
- loaderAsync = MyAsyncLoadFunction,
- };
- SmartReference.Runtime.SmartReference.Init(loader);
- ```
- - If you use Unity Resources(not recommended, see [why](https://medium.com/@bjjx1999/3-ways-to-reduce-load-time-in-runtime-for-unity-15d33003eb79)), call
- ```csharp
- SmartReference.Runtime.SmartReference.InitWithResourcesLoader();
- ```
-
-3. SmartReference will automatically update paths before player build in case you move or rename the referenced asset.
- If you want to manually update all references in the project, go to `Tools -> SmartReference -> Update All References` to update all references in the project.
+### 1. Replace direct Unity references with `SmartReference`
+In the Inspector, SmartReference looks and behaves like a normal object reference.
+```csharp
+using UnityEngine;
+using SmartReference.Runtime;
+
+public class MonsterData : ScriptableObject
+{
+ public SmartReference prefab;
+ public SmartReference icon;
+ public string description;
+}
+
+```
+
+### 2. Initialize Smart Reference at startup
+Choose the loader that matches your project setup.
+#### Addressables (recommended)
+```csharp
+using SmartReference.Runtime;
+
+SmartReference.InitWithAddressablesLoader();
+```
+
+#### Resources
+```csharp
+using SmartReference.Runtime;
+
+SmartReference.InitWithResourcesLoader();
+```
+
+#### Custom Loader
+```csharp
+SmartReference.InitWithCustomLoader(new MyCustomLoader());
+```
+
+Initialization must happen once, before any `SmartReference` is accessed.
+
+### 3. Load assets when needed
+#### Asynchronous Loading with UniTask (recommended)
+```csharp
+var prefab = await monsterData.prefab.LoadAsyncUniTask();
+Instantiate(prefab);
+```
+
+#### Asynchronous Loading with C# Async/Await
+```csharp
+var prefab = await monsterData.prefab.LoadAsyncTask();
+Instantiate(prefab);
+```
+
+#### Asynchronous Loading with Callbacks
+```csharp
+monsterData.prefab.OnAsyncLoadComplete += go =>
+{
+ Instantiate(go);
+};
+monsterData.prefab.LoadAsync();
+```
+
+#### Synchronous Loading
+```csharp
+var prefab = monsterData.prefab.Load();
+Instantiate(prefab);
+```
## Supports
-If you have any questions, please leave an issue at [GitHub](https://github.com/Brian-Jiang/SmartReference/issues).
+If you have questions or feedback:
+- Leave an issue at [GitHub Issues](https://github.com/Brian-Jiang/SmartReference/issues)
+- Leave a comment at [Asset Store](https://u3d.as/35Sh)
+- Email: [bjjx1999@live.com](mailto:bjjx1999@live.com)
+
Thank you for your support!
\ No newline at end of file
diff --git a/Runtime/Handles.meta b/Runtime/Handles.meta
new file mode 100644
index 0000000..17e075c
--- /dev/null
+++ b/Runtime/Handles.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f3b9f87c3cbe4e3c89a2f48978b5b4d4
+timeCreated: 1766125299
\ No newline at end of file
diff --git a/Runtime/Handles/AddressablesHandle.cs b/Runtime/Handles/AddressablesHandle.cs
new file mode 100644
index 0000000..08de078
--- /dev/null
+++ b/Runtime/Handles/AddressablesHandle.cs
@@ -0,0 +1,15 @@
+#if SMARTREFERENCE_ADDRESSABLES_SUPPORT
+
+using UnityEngine;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace SmartReference.Runtime
+{
+ public class AddressablesHandle : ISmartReferenceHandle
+ {
+ public AsyncOperationHandle